Merge "Make Glanceable Hub dimensions consistent between density changes." into main
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index ee03e4b..857154f 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -116,7 +116,6 @@
 import android.os.ShellCallback;
 import android.os.ShellCommand;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.ThreadLocalWorkSource;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -179,9 +178,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.text.SimpleDateFormat;
-import java.time.Instant;
-import java.time.zone.ZoneOffsetTransition;
-import java.time.zone.ZoneRules;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
@@ -193,7 +189,6 @@
 import java.util.TimeZone;
 import java.util.TreeSet;
 import java.util.concurrent.ThreadLocalRandom;
-import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 
 /**
@@ -233,13 +228,6 @@
 
     private static final long TEMPORARY_QUOTA_DURATION = INTERVAL_DAY;
 
-    // System properties read on some device configurations to initialize time properly and
-    // perform DST transitions at the bootloader level.
-    private static final String TIMEOFFSET_PROPERTY = "persist.sys.time.offset";
-    private static final String DST_TRANSITION_PROPERTY = "persist.sys.time.dst_transition";
-    private static final String DST_OFFSET_PROPERTY = "persist.sys.time.dst_offset";
-
-
     private final Intent mBackgroundIntent
             = new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
 
@@ -2127,22 +2115,6 @@
             // "GMT" if the ID is unrecognized). The parameter ID is used here rather than
             // newZone.getId(). It will be rejected if it is invalid.
             timeZoneWasChanged = SystemTimeZone.setTimeZoneId(tzId, confidence, logInfo);
-
-            final int gmtOffset = newZone.getOffset(mInjector.getCurrentTimeMillis());
-            SystemProperties.set(TIMEOFFSET_PROPERTY, String.valueOf(gmtOffset));
-
-            final ZoneRules rules = newZone.toZoneId().getRules();
-            final ZoneOffsetTransition transition = rules.nextTransition(Instant.now());
-            if (null != transition) {
-                // Get the offset between the time after the DST transition and before.
-                final long transitionOffset = TimeUnit.SECONDS.toMillis((
-                        transition.getOffsetAfter().getTotalSeconds()
-                        - transition.getOffsetBefore().getTotalSeconds()));
-                // Time when the next DST transition is programmed.
-                final long nextTransition = TimeUnit.SECONDS.toMillis(transition.toEpochSecond());
-                SystemProperties.set(DST_TRANSITION_PROPERTY, String.valueOf(nextTransition));
-                SystemProperties.set(DST_OFFSET_PROPERTY, String.valueOf(transitionOffset));
-            }
         }
 
         // Clear the default time zone in the system server process. This forces the next call
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 6113932..e8d7e1e 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -600,7 +600,7 @@
      *
      * <p>
      * This sensor must be able to detect and report an on-body to off-body
-     * transition within 1 second of the device being removed from the body,
+     * transition within 3 seconds of the device being removed from the body,
      * and must be able to detect and report an off-body to on-body transition
      * within 5 seconds of the device being put back onto the body.
      * </p>
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 0cd2800..b4ad050 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -75,3 +75,13 @@
     description: "Enables a developer overlay that displays raw touchpad input data and gesture recognition status in real-time."
     bug: "286551975"
 }
+
+flag {
+    namespace: "input_native"
+    name: "keyboard_layout_manager_multi_user_ime_setup"
+    description: "Update KeyboardLayoutManager to work correctly with multi-user IME setup"
+    bug: "354333072"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 71c83f2..99bd67b 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -497,7 +497,27 @@
      */
     @RequiresPermission(android.Manifest.permission.VIBRATE)
     public void vibrate(@NonNull VibrationEffect vibe, @NonNull VibrationAttributes attributes) {
-        vibrate(Process.myUid(), mPackageName, vibe, null, attributes);
+        vibrate(vibe, attributes, null);
+    }
+
+    /**
+     * Vibrate with a given effect.
+     *
+     * <p>The app should be in the foreground for the vibration to happen. Background apps should
+     * specify a ringtone, notification or alarm usage in order to vibrate.</p>
+     *
+     * @param vibe       {@link VibrationEffect} describing the vibration to be performed.
+     * @param attributes {@link VibrationAttributes} corresponding to the vibration. For example,
+     *                   specify {@link VibrationAttributes#USAGE_ALARM} for alarm vibrations or
+     *                   {@link VibrationAttributes#USAGE_RINGTONE} for vibrations associated with
+     *                   incoming calls.
+     * @param reason     the reason for this vibration, used for debugging purposes.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public void vibrate(@NonNull VibrationEffect vibe,
+            @NonNull VibrationAttributes attributes, @NonNull String reason) {
+        vibrate(Process.myUid(), mPackageName, vibe, reason, attributes);
     }
 
     /**
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 708c196..192afb1 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -28,6 +28,7 @@
 import android.annotation.SystemApi;
 import android.annotation.UserHandleAware;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -1624,6 +1625,19 @@
                 "is_call_log_phone_account_migration_pending";
 
         /**
+         * The default maximum number of call log entries stored in the call log provider for each
+         * {@link PhoneAccountHandle}.
+         */
+        private static final int DEFAULT_MAX_CALL_LOG_SIZE = 500;
+
+        /**
+         * Expected component name of Telephony phone accounts.
+         */
+        private static final ComponentName TELEPHONY_COMPONENT_NAME =
+                new ComponentName("com.android.phone",
+                        "com.android.services.telephony.TelephonyConnectionService");
+
+        /**
          * Adds a call to the call log.
          *
          * @param ci the CallerInfo object to get the target contact from.  Can be null
@@ -2084,25 +2098,35 @@
                 }
 
                 int numDeleted;
-                if (values.containsKey(PHONE_ACCOUNT_ID)
-                        && !TextUtils.isEmpty(values.getAsString(PHONE_ACCOUNT_ID))
-                        && values.containsKey(PHONE_ACCOUNT_COMPONENT_NAME)
-                        && !TextUtils.isEmpty(values.getAsString(PHONE_ACCOUNT_COMPONENT_NAME))) {
+                final String phoneAccountId =
+                        values.containsKey(PHONE_ACCOUNT_ID)
+                                ? values.getAsString(PHONE_ACCOUNT_ID) : null;
+                final String phoneAccountComponentName =
+                        values.containsKey(PHONE_ACCOUNT_COMPONENT_NAME)
+                                ? values.getAsString(PHONE_ACCOUNT_COMPONENT_NAME) : null;
+                int maxCallLogSize = DEFAULT_MAX_CALL_LOG_SIZE;
+                if (!TextUtils.isEmpty(phoneAccountId)
+                        && !TextUtils.isEmpty(phoneAccountComponentName)) {
+                    if (android.provider.Flags.allowConfigMaximumCallLogEntriesPerSim()
+                            && TELEPHONY_COMPONENT_NAME
+                                    .flattenToString().equals(phoneAccountComponentName)) {
+                        maxCallLogSize = context.getResources().getInteger(
+                                com.android.internal.R.integer.config_maximumCallLogEntriesPerSim);
+                    }
                     // Only purge entries for the same phone account.
                     numDeleted = resolver.delete(uri, "_id IN "
                             + "(SELECT _id FROM calls"
                             + " WHERE " + PHONE_ACCOUNT_COMPONENT_NAME + " = ?"
                             + " AND " + PHONE_ACCOUNT_ID + " = ?"
                             + " ORDER BY " + DEFAULT_SORT_ORDER
-                            + " LIMIT -1 OFFSET 500)", new String[] {
-                            values.getAsString(PHONE_ACCOUNT_COMPONENT_NAME),
-                            values.getAsString(PHONE_ACCOUNT_ID)
-                    });
+                            + " LIMIT -1 OFFSET " + maxCallLogSize + ")",
+                            new String[] { phoneAccountComponentName, phoneAccountId }
+                    );
                 } else {
                     // No valid phone account specified, so default to the old behavior.
                     numDeleted = resolver.delete(uri, "_id IN "
                             + "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER
-                            + " LIMIT -1 OFFSET 500)", null);
+                            + " LIMIT -1 OFFSET " + maxCallLogSize + ")", null);
                 }
                 Log.i(LOG_TAG, "addEntry: cleaned up " + numDeleted + " old entries");
 
diff --git a/core/java/android/provider/flags.aconfig b/core/java/android/provider/flags.aconfig
index ff98fc4..53d0c62 100644
--- a/core/java/android/provider/flags.aconfig
+++ b/core/java/android/provider/flags.aconfig
@@ -30,4 +30,16 @@
     namespace: "backstage_power"
     description: "Add a new settings page for the RUN_BACKUP_JOBS permission."
     bug: "320563660"
-}
\ No newline at end of file
+}
+
+# OWNER = tgunn TARGET=25Q1
+flag {
+    name: "allow_config_maximum_call_log_entries_per_sim"
+    is_exported: true
+    namespace: "telecom"
+    description: "Allow partners to modify the maximum number of call log size for each sim card."
+    bug: "352235494"
+    metadata {
+        purpose: PURPOSE_FEATURE
+    }
+}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 46b222b..66d08f9 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -404,8 +404,7 @@
             }
 
             private void prepareToDraw() {
-                if (mDisplayState == Display.STATE_DOZE
-                        || mDisplayState == Display.STATE_DOZE_SUSPEND) {
+                if (mDisplayState == Display.STATE_DOZE) {
                     try {
                         mSession.pokeDrawLock(mWindow);
                     } catch (RemoteException e) {
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index b07534f..5d84d17 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -68,11 +68,4 @@
     public static boolean fixMisalignedContextMenu() {
         return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU);
     }
-
-    /**
-     * @see Flags#clearFontVariationSettings()
-     */
-    public static boolean clearFontVariationSettings() {
-        return TextFlags.isFeatureEnabled(Flags.FLAG_CLEAR_FONT_VARIATION_SETTINGS);
-    }
 }
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 4dca284..9e02460 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -61,7 +61,6 @@
             Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE,
             Flags.FLAG_ICU_BIDI_MIGRATION,
             Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU,
-            Flags.FLAG_CLEAR_FONT_VARIATION_SETTINGS,
     };
 
     /**
@@ -76,7 +75,6 @@
             Flags.fixLineHeightForLocale(),
             Flags.icuBidiMigration(),
             Flags.fixMisalignedContextMenu(),
-            Flags.clearFontVariationSettings(),
     };
 
     /**
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 5c10db1..b796e0b 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -104,12 +104,23 @@
      */
     public static final int FLAG_ANIMATE_RESIZING = 1 << 3;
 
+    /**
+     * Controls whether the {@link WindowInsets.Type#captionBar()} insets provided by this source
+     * should always be forcibly consumed. Unlike with {@link #FLAG_FORCE_CONSUMING}, when this
+     * flag is used the caption bar will be consumed even when the bar is requested to be visible.
+     *
+     * Note: this flag does not take effect when the window applies
+     * {@link WindowInsetsController.Appearance#APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND}.
+     */
+    public static final int FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR = 1 << 4;
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, prefix = "FLAG_", value = {
             FLAG_SUPPRESS_SCRIM,
             FLAG_INSETS_ROUNDED_CORNER,
             FLAG_FORCE_CONSUMING,
             FLAG_ANIMATE_RESIZING,
+            FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR
     })
     public @interface Flags {}
 
@@ -555,6 +566,9 @@
         if ((flags & FLAG_ANIMATE_RESIZING) != 0) {
             joiner.add("ANIMATE_RESIZING");
         }
+        if ((flags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0) {
+            joiner.add("FORCE_CONSUMING_OPAQUE_CAPTION_BAR");
+        }
         return joiner.toString();
     }
 
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index bbd9acf..6b4340a 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.util.SequenceUtils.getInitSeq;
 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
 import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
 import static android.view.InsetsStateProto.DISPLAY_CUTOUT;
 import static android.view.InsetsStateProto.DISPLAY_FRAME;
@@ -54,6 +55,7 @@
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
 
 import java.io.PrintWriter;
 import java.util.Objects;
@@ -131,18 +133,25 @@
         final Rect relativeFrame = new Rect(frame);
         final Rect relativeFrameMax = new Rect(frame);
         @InsetsType int forceConsumingTypes = 0;
+        boolean forceConsumingOpaqueCaptionBar = false;
         @InsetsType int suppressScrimTypes = 0;
         final Rect[][] typeBoundingRectsMap = new Rect[Type.SIZE][];
         final Rect[][] typeMaxBoundingRectsMap = new Rect[Type.SIZE][];
         for (int i = mSources.size() - 1; i >= 0; i--) {
             final InsetsSource source = mSources.valueAt(i);
             final @InsetsType int type = source.getType();
+            final @InsetsSource.Flags int flags = source.getFlags();
 
-            if ((source.getFlags() & InsetsSource.FLAG_FORCE_CONSUMING) != 0) {
+            if ((flags & InsetsSource.FLAG_FORCE_CONSUMING) != 0) {
                 forceConsumingTypes |= type;
             }
 
-            if ((source.getFlags() & InsetsSource.FLAG_SUPPRESS_SCRIM) != 0) {
+            if (Flags.enableCaptionCompatInsetForceConsumptionAlways()
+                    && (flags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0) {
+                forceConsumingOpaqueCaptionBar = true;
+            }
+
+            if ((flags & InsetsSource.FLAG_SUPPRESS_SCRIM) != 0) {
                 suppressScrimTypes |= type;
             }
 
@@ -177,7 +186,8 @@
         }
 
         return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
-                forceConsumingTypes, suppressScrimTypes, calculateRelativeCutout(frame),
+                forceConsumingTypes, forceConsumingOpaqueCaptionBar, suppressScrimTypes,
+                calculateRelativeCutout(frame),
                 calculateRelativeRoundedCorners(frame),
                 calculateRelativePrivacyIndicatorBounds(frame),
                 calculateRelativeDisplayShape(frame),
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 987c8c8..e3ea6b22 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -97,6 +97,7 @@
     private final int mFrameHeight;
 
     private final @InsetsType int mForceConsumingTypes;
+    private final boolean mForceConsumingOpaqueCaptionBar;
     private final @InsetsType int mSuppressScrimTypes;
     private final boolean mSystemWindowInsetsConsumed;
     private final boolean mStableInsetsConsumed;
@@ -123,7 +124,7 @@
 
     static {
         CONSUMED = new WindowInsets(createCompatTypeMap(null), createCompatTypeMap(null),
-                createCompatVisibilityMap(createCompatTypeMap(null)), false, 0, 0, null,
+                createCompatVisibilityMap(createCompatTypeMap(null)), false, 0, false, 0, null,
                 null, null, null, systemBars(), false, null, null, 0, 0);
     }
 
@@ -144,6 +145,7 @@
             boolean[] typeVisibilityMap,
             boolean isRound,
             @InsetsType int forceConsumingTypes,
+            boolean forceConsumingOpaqueCaptionBar,
             @InsetsType int suppressScrimTypes,
             DisplayCutout displayCutout,
             RoundedCorners roundedCorners,
@@ -166,6 +168,7 @@
         mTypeVisibilityMap = typeVisibilityMap;
         mIsRound = isRound;
         mForceConsumingTypes = forceConsumingTypes;
+        mForceConsumingOpaqueCaptionBar = forceConsumingOpaqueCaptionBar;
         mSuppressScrimTypes = suppressScrimTypes;
         mCompatInsetsTypes = compatInsetsTypes;
         mCompatIgnoreVisibility = compatIgnoreVisibility;
@@ -196,7 +199,9 @@
         this(src.mSystemWindowInsetsConsumed ? null : src.mTypeInsetsMap,
                 src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap,
                 src.mTypeVisibilityMap, src.mIsRound,
-                src.mForceConsumingTypes, src.mSuppressScrimTypes,
+                src.mForceConsumingTypes,
+                src.mForceConsumingOpaqueCaptionBar,
+                src.mSuppressScrimTypes,
                 displayCutoutCopyConstructorArgument(src),
                 src.mRoundedCorners,
                 src.mPrivacyIndicatorBounds,
@@ -257,7 +262,7 @@
     /** @hide */
     @UnsupportedAppUsage
     public WindowInsets(Rect systemWindowInsets) {
-        this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, 0, 0,
+        this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, 0, false, 0,
                 null, null, null, null, systemBars(), false /* compatIgnoreVisibility */,
                 new Rect[SIZE][], null, 0, 0);
     }
@@ -675,10 +680,10 @@
         return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap,
                 mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
                 mTypeVisibilityMap,
-                mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
-                null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape,
-                mCompatInsetsTypes, mCompatIgnoreVisibility,
-                mSystemWindowInsetsConsumed ? null : mTypeBoundingRectsMap,
+                mIsRound, mForceConsumingTypes, mForceConsumingOpaqueCaptionBar,
+                mSuppressScrimTypes, null /* displayCutout */, mRoundedCorners,
+                mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes,
+                mCompatIgnoreVisibility, mSystemWindowInsetsConsumed ? null : mTypeBoundingRectsMap,
                 mStableInsetsConsumed ? null : mTypeMaxBoundingRectsMap,
                 mFrameWidth, mFrameHeight);
     }
@@ -729,7 +734,8 @@
     public WindowInsets consumeSystemWindowInsets() {
         return new WindowInsets(null, null,
                 mTypeVisibilityMap,
-                mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
+                mIsRound, mForceConsumingTypes, mForceConsumingOpaqueCaptionBar,
+                mSuppressScrimTypes,
                 // If the system window insets types contain displayCutout, we should also consume
                 // it.
                 (mCompatInsetsTypes & displayCutout()) != 0
@@ -1024,6 +1030,13 @@
     /**
      * @hide
      */
+    public boolean isForceConsumingOpaqueCaptionBar() {
+        return mForceConsumingOpaqueCaptionBar;
+    }
+
+    /**
+     * @hide
+     */
     public @InsetsType int getSuppressScrimTypes() {
         return mSuppressScrimTypes;
     }
@@ -1058,6 +1071,8 @@
         result.append("\n    ");
         result.append("forceConsumingTypes=" + Type.toString(mForceConsumingTypes));
         result.append("\n    ");
+        result.append("forceConsumingOpaqueCaptionBar=" + mForceConsumingOpaqueCaptionBar);
+        result.append("\n    ");
         result.append("suppressScrimTypes=" + Type.toString(mSuppressScrimTypes));
         result.append("\n    ");
         result.append("compatInsetsTypes=" + Type.toString(mCompatInsetsTypes));
@@ -1180,7 +1195,8 @@
                         ? null
                         : insetInsets(mTypeMaxInsetsMap, left, top, right, bottom),
                 mTypeVisibilityMap,
-                mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
+                mIsRound, mForceConsumingTypes, mForceConsumingOpaqueCaptionBar,
+                mSuppressScrimTypes,
                 mDisplayCutoutConsumed
                         ? null
                         : mDisplayCutout == null
@@ -1214,6 +1230,7 @@
 
         return mIsRound == that.mIsRound
                 && mForceConsumingTypes == that.mForceConsumingTypes
+                && mForceConsumingOpaqueCaptionBar == that.mForceConsumingOpaqueCaptionBar
                 && mSuppressScrimTypes == that.mSuppressScrimTypes
                 && mSystemWindowInsetsConsumed == that.mSystemWindowInsetsConsumed
                 && mStableInsetsConsumed == that.mStableInsetsConsumed
@@ -1235,9 +1252,9 @@
     public int hashCode() {
         return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap),
                 Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners,
-                mForceConsumingTypes, mSuppressScrimTypes, mSystemWindowInsetsConsumed,
-                mStableInsetsConsumed, mDisplayCutoutConsumed, mPrivacyIndicatorBounds,
-                mDisplayShape, Arrays.deepHashCode(mTypeBoundingRectsMap),
+                mForceConsumingTypes, mForceConsumingOpaqueCaptionBar, mSuppressScrimTypes,
+                mSystemWindowInsetsConsumed, mStableInsetsConsumed, mDisplayCutoutConsumed,
+                mPrivacyIndicatorBounds, mDisplayShape, Arrays.deepHashCode(mTypeBoundingRectsMap),
                 Arrays.deepHashCode(mTypeMaxBoundingRectsMap), mFrameWidth, mFrameHeight);
     }
 
@@ -1367,6 +1384,7 @@
 
         private boolean mIsRound;
         private @InsetsType int mForceConsumingTypes;
+        private boolean mForceConsumingOpaqueCaptionBar;
         private @InsetsType int mSuppressScrimTypes;
 
         private PrivacyIndicatorBounds mPrivacyIndicatorBounds = new PrivacyIndicatorBounds();
@@ -1399,6 +1417,7 @@
             mRoundedCorners = insets.mRoundedCorners;
             mIsRound = insets.mIsRound;
             mForceConsumingTypes = insets.mForceConsumingTypes;
+            mForceConsumingOpaqueCaptionBar = insets.mForceConsumingOpaqueCaptionBar;
             mSuppressScrimTypes = insets.mSuppressScrimTypes;
             mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds;
             mDisplayShape = insets.mDisplayShape;
@@ -1687,6 +1706,13 @@
 
         /** @hide */
         @NonNull
+        public Builder setForceConsumingOpaqueCaptionBar(boolean forceConsumingOpaqueCaptionBar) {
+            mForceConsumingOpaqueCaptionBar = forceConsumingOpaqueCaptionBar;
+            return this;
+        }
+
+        /** @hide */
+        @NonNull
         public Builder setSuppressScrimTypes(@InsetsType int suppressScrimTypes) {
             mSuppressScrimTypes = suppressScrimTypes;
             return this;
@@ -1765,9 +1791,9 @@
         public WindowInsets build() {
             return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
                     mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
-                    mIsRound, mForceConsumingTypes, mSuppressScrimTypes, mDisplayCutout,
-                    mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, systemBars(),
-                    false /* compatIgnoreVisibility */,
+                    mIsRound, mForceConsumingTypes, mForceConsumingOpaqueCaptionBar,
+                    mSuppressScrimTypes, mDisplayCutout, mRoundedCorners, mPrivacyIndicatorBounds,
+                    mDisplayShape, systemBars(), false /* compatIgnoreVisibility */,
                     mSystemInsetsConsumed ? null : mTypeBoundingRectsMap,
                     mStableInsetsConsumed ? null : mTypeMaxBoundingRectsMap,
                     mFrameWidth, mFrameHeight);
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 87e22ed..4828393 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -29,6 +29,7 @@
 import static android.view.WindowInsetsController.APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
@@ -36,6 +37,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.flags.Flags.customizableWindowHeaders;
 
 import static com.android.internal.policy.PhoneWindow.FEATURE_OPTIONS_PANEL;
 
@@ -226,6 +228,7 @@
     private boolean mLastHasLeftStableInset = false;
     private int mLastWindowFlags = 0;
     private @InsetsType int mLastForceConsumingTypes = 0;
+    private boolean mLastForceConsumingOpaqueCaptionBar = false;
     private @InsetsType int mLastSuppressScrimTypes = 0;
 
     private int mRootScrollY = 0;
@@ -1068,8 +1071,12 @@
         WindowManager.LayoutParams attrs = mWindow.getAttributes();
         int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
 
+        final ViewRootImpl viewRoot = getViewRootImpl();
         final WindowInsetsController controller = getWindowInsetsController();
         final @InsetsType int requestedVisibleTypes = controller.getRequestedVisibleTypes();
+        final @Appearance int appearance = viewRoot != null
+                ? viewRoot.mWindowAttributes.insetsFlags.appearance
+                : controller.getSystemBarsAppearance();
 
         // IME is an exceptional floating window that requires color view.
         final boolean isImeWindow =
@@ -1080,13 +1087,9 @@
                     & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
             mLastWindowFlags = attrs.flags;
 
-            final ViewRootImpl viewRoot = getViewRootImpl();
-            final @Appearance int appearance = viewRoot != null
-                    ? viewRoot.mWindowAttributes.insetsFlags.appearance
-                    : controller.getSystemBarsAppearance();
-
             if (insets != null) {
                 mLastForceConsumingTypes = insets.getForceConsumingTypes();
+                mLastForceConsumingOpaqueCaptionBar = insets.isForceConsumingOpaqueCaptionBar();
 
                 final boolean clearsCompatInsets = clearsCompatInsets(attrs.type, attrs.flags,
                         getResources().getConfiguration().windowConfiguration.getActivityType(),
@@ -1209,16 +1212,20 @@
 
         final boolean hideCaptionBar = fullscreen
                 || (requestedVisibleTypes & WindowInsets.Type.captionBar()) == 0;
-        final boolean consumingCaptionBar =
-                ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0
+        final boolean consumingCaptionBar = Flags.enableCaptionCompatInsetForceConsumption()
+                && ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0
                         && hideCaptionBar);
 
-        final int consumedTop;
-        if (Flags.enableCaptionCompatInsetForceConsumption()) {
-            consumedTop = (consumingStatusBar || consumingCaptionBar) ? mLastTopInset : 0;
-        } else {
-            consumedTop = consumingStatusBar ? mLastTopInset : 0;
-        }
+        final boolean isOpaqueCaptionBar = customizableWindowHeaders()
+                && (appearance & APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) == 0;
+        final boolean consumingOpaqueCaptionBar =
+                Flags.enableCaptionCompatInsetForceConsumptionAlways()
+                        && mLastForceConsumingOpaqueCaptionBar
+                        && isOpaqueCaptionBar;
+
+        final int consumedTop =
+                (consumingStatusBar || consumingCaptionBar || consumingOpaqueCaptionBar)
+                        ? mLastTopInset : 0;
         int consumedRight = consumingNavBar ? mLastRightInset : 0;
         int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
         int consumedLeft = consumingNavBar ? mLastLeftInset : 0;
diff --git a/core/java/com/android/internal/policy/SystemBarUtils.java b/core/java/com/android/internal/policy/SystemBarUtils.java
index efa3697..4ed15fa 100644
--- a/core/java/com/android/internal/policy/SystemBarUtils.java
+++ b/core/java/com/android/internal/policy/SystemBarUtils.java
@@ -92,4 +92,11 @@
         // Equals to status bar height if status bar height is bigger.
         return Math.max(defaultSize, statusBarHeight);
     }
+
+    /**
+     * Gets the taskbar frame height.
+     */
+    public static int getTaskbarHeight(Resources res) {
+        return res.getDimensionPixelSize(R.dimen.taskbar_frame_height);
+    }
 }
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 809ec63..e5ac0e1 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -33,6 +33,7 @@
 
 #include <algorithm>
 #include <array>
+#include <cstring>
 #include <limits>
 #include <memory>
 #include <string>
@@ -50,7 +51,6 @@
 #include <inttypes.h>
 #include <pwd.h>
 #include <signal.h>
-#include <string.h>
 #include <sys/epoll.h>
 #include <sys/errno.h>
 #include <sys/pidfd.h>
@@ -73,13 +73,13 @@
 // readProcFile() are reading files under this threshold, e.g.,
 // /proc/pid/stat.  /proc/pid/time_in_state ends up being about 520
 // bytes, so use 1024 for the stack to provide a bit of slack.
-static constexpr ssize_t kProcReadStackBufferSize = 1024;
+static constexpr size_t kProcReadStackBufferSize = 1024;
 
 // The other files we read from proc tend to be a bit larger (e.g.,
 // /proc/stat is about 3kB), so once we exhaust the stack buffer,
 // retry with a relatively large heap-allocated buffer.  We double
 // this size and retry until the whole file fits.
-static constexpr ssize_t kProcReadMinHeapBufferSize = 4096;
+static constexpr size_t kProcReadMinHeapBufferSize = 4096;
 
 #if GUARD_THREAD_PRIORITY
 Mutex gKeyCreateMutex;
@@ -817,7 +817,6 @@
     }
 
     DIR* dirp = opendir(file8);
-
     env->ReleaseStringUTFChars(file, file8);
 
     if(dirp == NULL) {
@@ -850,6 +849,7 @@
             jintArray newArray = env->NewIntArray(newCount);
             if (newArray == NULL) {
                 closedir(dirp);
+                if (curData) env->ReleaseIntArrayElements(lastArray, curData, 0);
                 jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
                 return NULL;
             }
@@ -1046,78 +1046,71 @@
         return JNI_FALSE;
     }
 
-    const char* file8 = env->GetStringUTFChars(file, NULL);
-    if (file8 == NULL) {
+    auto releaser = [&](const char* jniStr) { env->ReleaseStringUTFChars(file, jniStr); };
+    std::unique_ptr<const char[], decltype(releaser)> file8(env->GetStringUTFChars(file, NULL),
+                                                            releaser);
+    if (!file8) {
         jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
         return JNI_FALSE;
     }
 
-    ::android::base::unique_fd fd(open(file8, O_RDONLY | O_CLOEXEC));
+    ::android::base::unique_fd fd(open(file8.get(), O_RDONLY | O_CLOEXEC));
     if (!fd.ok()) {
         if (kDebugProc) {
-            ALOGW("Unable to open process file: %s\n", file8);
+            ALOGW("Unable to open process file: %s\n", file8.get());
         }
-        env->ReleaseStringUTFChars(file, file8);
         return JNI_FALSE;
     }
-    env->ReleaseStringUTFChars(file, file8);
 
     // Most proc files we read are small, so we go through the loop
-    // with the stack buffer firstly. We allocate a buffer big
-    // enough for the whole file.
+    // with the stack buffer first. We allocate a buffer big enough
+    // for most files.
 
-    char readBufferStack[kProcReadStackBufferSize];
-    std::unique_ptr<char[]> readBufferHeap;
-    char* readBuffer = &readBufferStack[0];
-    ssize_t readBufferSize = kProcReadStackBufferSize;
-    ssize_t numberBytesRead;
+    char stackBuf[kProcReadStackBufferSize];
+    std::vector<char> heapBuf;
+    char* buf = stackBuf;
+
+    size_t remaining = sizeof(stackBuf);
     off_t offset = 0;
-    for (;;) {
-        ssize_t requestedBufferSize = readBufferSize - offset;
-        // By using pread, we can avoid an lseek to rewind the FD
-        // before retry, saving a system call.
-        numberBytesRead =
-                TEMP_FAILURE_RETRY(pread(fd, readBuffer + offset, requestedBufferSize, offset));
-        if (numberBytesRead < 0) {
+    ssize_t numBytesRead;
+
+    do {
+        numBytesRead = TEMP_FAILURE_RETRY(pread(fd, buf + offset, remaining, offset));
+        if (numBytesRead < 0) {
             if (kDebugProc) {
                 ALOGW("Unable to read process file err: %s file: %s fd=%d\n",
-                      strerror_r(errno, &readBufferStack[0], sizeof(readBufferStack)), file8,
-                      fd.get());
+                      strerror_r(errno, stackBuf, sizeof(stackBuf)), file8.get(), fd.get());
             }
             return JNI_FALSE;
         }
-        if (numberBytesRead == 0) {
-            // End of file.
-            numberBytesRead = offset;
-            break;
-        }
-        if (numberBytesRead < requestedBufferSize) {
-            // Read less bytes than requested, it's not an error per pread(2).
-            offset += numberBytesRead;
-        } else {
-            // Buffer is fully used, try to grow it.
-            if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
-                if (kDebugProc) {
-                    ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
+
+        offset += numBytesRead;
+        remaining -= numBytesRead;
+
+        if (numBytesRead && !remaining) {
+            if (buf == stackBuf) {
+                heapBuf.resize(kProcReadMinHeapBufferSize);
+                static_assert(kProcReadMinHeapBufferSize > sizeof(stackBuf));
+                std::memcpy(heapBuf.data(), stackBuf, sizeof(stackBuf));
+            } else {
+                constexpr size_t MAX_READABLE_PROCFILE_SIZE = 64 << 20;
+                if (heapBuf.size() >= MAX_READABLE_PROCFILE_SIZE) {
+                    if (kDebugProc) {
+                        ALOGW("Proc file too big: %s fd=%d size=%zu\n",
+                              file8.get(), fd.get(), heapBuf.size());
+                    }
+                    return JNI_FALSE;
                 }
-                return JNI_FALSE;
+                heapBuf.resize(2 * heapBuf.size());
             }
-            readBufferSize = std::max(readBufferSize * 2, kProcReadMinHeapBufferSize);
-            readBufferHeap.reset(); // Free address space before getting more.
-            readBufferHeap = std::make_unique<char[]>(readBufferSize);
-            if (!readBufferHeap) {
-                jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
-                return JNI_FALSE;
-            }
-            readBuffer = readBufferHeap.get();
-            offset = 0;
+            buf = heapBuf.data();
+            remaining = heapBuf.size() - offset;
         }
-    }
+    } while (numBytesRead != 0);
 
     // parseProcLineArray below modifies the buffer while parsing!
     return android_os_Process_parseProcLineArray(
-        env, clazz, readBuffer, 0, numberBytesRead,
-        format, outStrings, outLongs, outFloats);
+        env, clazz, buf, 0, offset, format, outStrings, outLongs, outFloats);
 }
 
 void android_os_Process_setApplicationObject(JNIEnv* env, jobject clazz,
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8cb7646..2afc303 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7100,4 +7100,8 @@
     <!-- Whether to enable the private space search illustration and search tile content in "Hide Private Space" settings page.
          OEM/Partner can explicitly opt to hide the illustration and search tile content. -->
     <bool name="config_enableSearchTileHideIllustrationInPrivateSpace">true</bool>
+
+    <!-- The maximum number of call log entries for each sim card that can be stored in the call log
+         provider on the device. -->
+    <integer name="config_maximumCallLogEntriesPerSim">500</integer>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4a425cb..bc8c778 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5530,6 +5530,8 @@
   <java-symbol type="integer" name="config_wallpaperFrameRateCompatibility" />
 
   <java-symbol type="integer" name="config_defaultMinEmergencyGestureTapDurationMillis" />
+  <java-symbol type="integer" name="config_maximumCallLogEntriesPerSim" />
+
   <!-- Back swipe thresholds -->
   <java-symbol type="dimen" name="navigation_edge_action_progress_threshold" />
   <java-symbol type="dimen" name="back_progress_non_linear_factor" />
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 16bd20a..75749c7 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
 import static android.view.InsetsSource.ID_IME;
 import static android.view.InsetsSource.SIDE_BOTTOM;
 import static android.view.InsetsSource.SIDE_TOP;
@@ -54,12 +55,17 @@
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Parcel;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.SparseIntArray;
 import android.view.WindowInsets.Type;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.window.flags.Flags;
+
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -78,6 +84,9 @@
 @RunWith(AndroidJUnit4.class)
 public class InsetsStateTest {
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final int ID_STATUS_BAR = InsetsSource.createId(
             null /* owner */, 0 /* index */, statusBars());
     private static final int ID_NAVIGATION_BAR = InsetsSource.createId(
@@ -854,4 +863,19 @@
         );
 
     }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS)
+    public void testCalculateInsets_forceConsumingCaptionBar() {
+        mState.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setVisible(true)
+                .setFlags(FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR);
+
+        final WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 1000, 1000), null, false,
+                SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
+                new SparseIntArray());
+
+        assertTrue(insets.isForceConsumingOpaqueCaptionBar());
+    }
 }
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index ba1204b..c86045d 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -43,14 +43,14 @@
     @Test
     public void systemWindowInsets_afterConsuming_isConsumed() {
         assertTrue(new WindowInsets(WindowInsets.createCompatTypeMap(new Rect(1, 2, 3, 4)), null,
-                null, false, 0, 0, null, null, null, null,
+                null, false, 0, false, 0, null, null, null, null,
                 WindowInsets.Type.systemBars(), false, null, null, 0, 0)
                 .consumeSystemWindowInsets().isConsumed());
     }
 
     @Test
     public void multiNullConstructor_isConsumed() {
-        assertTrue(new WindowInsets(null, null, null, false, 0, 0, null, null, null, null,
+        assertTrue(new WindowInsets(null, null, null, false, 0, false, 0, null, null, null, null,
                 WindowInsets.Type.systemBars(), false, null, null, 0, 0).isConsumed());
     }
 
@@ -67,7 +67,7 @@
         WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0));
         WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0));
         WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, 0,
-                0, null, null, null, DisplayShape.NONE, systemBars(),
+                false, 0, null, null, null, DisplayShape.NONE, systemBars(),
                 true /* compatIgnoreVisibility */, null, null, 0, 0);
         assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets());
     }
diff --git a/core/tests/vibrator/src/android/os/VibratorTest.java b/core/tests/vibrator/src/android/os/VibratorTest.java
index cfa12bb..6210a00 100644
--- a/core/tests/vibrator/src/android/os/VibratorTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorTest.java
@@ -222,6 +222,18 @@
     }
 
     @Test
+    public void vibrate_withVibrationAttributesAndReason_usesGivenAttributesAndReason() {
+        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+        VibrationAttributes attributes = new VibrationAttributes.Builder().setUsage(
+                VibrationAttributes.USAGE_TOUCH).build();
+        String reason = "reason";
+
+        mVibratorSpy.vibrate(effect, attributes, reason);
+
+        verify(mVibratorSpy).vibrate(anyInt(), anyString(), eq(effect), eq(reason), eq(attributes));
+    }
+
+    @Test
     public void vibrate_withAudioAttributes_createsVibrationAttributesWithSameUsage() {
         VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
         AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 5d4139e..1fe6ca7 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -505,6 +505,7 @@
         <permission name="android.permission.RENOUNCE_PERMISSIONS" />
         <permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
         <permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" />
+        <permission name="android.permission.READ_DROPBOX_DATA" />
         <permission name="android.permission.READ_LOGS" />
         <permission name="android.permission.BRIGHTNESS_SLIDER_USAGE" />
         <permission name="android.permission.ACCESS_AMBIENT_LIGHT_STATS" />
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index b83931f..df95a91 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -35,7 +35,6 @@
 import android.graphics.fonts.FontVariationAxis;
 import android.os.Build;
 import android.os.LocaleList;
-import android.text.ClientFlags;
 import android.text.GraphicsOperations;
 import android.text.SpannableString;
 import android.text.SpannedString;
@@ -1541,21 +1540,8 @@
      * @return         typeface
      */
     public Typeface setTypeface(Typeface typeface) {
-        return setTypefaceInternal(typeface, true);
-    }
-
-    private Typeface setTypefaceInternal(Typeface typeface, boolean clearFontVariationSettings) {
         final long typefaceNative = typeface == null ? 0 : typeface.native_instance;
         nSetTypeface(mNativePaint, typefaceNative);
-
-        if (ClientFlags.clearFontVariationSettings()) {
-            if (clearFontVariationSettings && !Objects.equals(mTypeface, typeface)) {
-                // We cannot call setFontVariationSetting with empty string or null because it calls
-                // setTypeface method. To avoid recursive setTypeface call, manually resetting
-                // mFontVariationSettings.
-                mFontVariationSettings = null;
-            }
-        }
         mTypeface = typeface;
         return typeface;
     }
@@ -2051,14 +2037,6 @@
      * </li>
      * </ul>
      *
-     * Note: This method replaces the Typeface previously set to this instance.
-     * Until API {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, any caller of
-     * {@link #setTypeface(Typeface)} should call this method with empty settings, then call
-     * {@link #setTypeface(Typeface)}, then call this method with preferred variation settings.
-     * The device API more than {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, the
-     * {@link #setTypeface(Typeface)} method clears font variation settings. So caller of
-     * {@link #setTypeface(Typeface)} should call this method again for applying variation settings.
-     *
      * @param fontVariationSettings font variation settings. You can pass null or empty string as
      *                              no variation settings.
      *
@@ -2081,8 +2059,8 @@
 
         if (settings == null || settings.length() == 0) {
             mFontVariationSettings = null;
-            setTypefaceInternal(Typeface.createFromTypefaceWithVariation(mTypeface,
-                      Collections.emptyList()), false);
+            setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface,
+                      Collections.emptyList()));
             return true;
         }
 
@@ -2100,8 +2078,7 @@
             return false;
         }
         mFontVariationSettings = settings;
-        setTypefaceInternal(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes),
-                false);
+        setTypeface(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes));
         return true;
     }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index 544f0f3..882a8d0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -565,6 +565,7 @@
         return true;
     }
 
+    // Only called by onTouch() and mRenderer is already null-checked.
     @GuardedBy("mLock")
     private void onStartDragging(@NonNull MotionEvent event) {
         mVelocityTracker = VelocityTracker.obtain();
@@ -590,6 +591,7 @@
         });
     }
 
+    // Only called by onTouch() and mRenderer is already null-checked.
     @GuardedBy("mLock")
     private void onDrag(@NonNull MotionEvent event) {
         if (mVelocityTracker != null) {
@@ -660,8 +662,10 @@
 
     @GuardedBy("mLock")
     private void updateDividerPosition(int position) {
-        mRenderer.setDividerPosition(position);
-        mRenderer.updateSurface();
+        if (mRenderer != null) {
+            mRenderer.setDividerPosition(position);
+            mRenderer.updateSurface();
+        }
     }
 
     @GuardedBy("mLock")
@@ -669,7 +673,10 @@
         // Veil visibility change should be applied together with the surface boost transaction in
         // the wct.
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-        mRenderer.hideVeils(t);
+
+        if (mRenderer != null) {
+            mRenderer.hideVeils(t);
+        }
 
         // Callbacks must be executed on the executor to release mLock and prevent deadlocks.
         // mDecorSurfaceOwner may change between here and when the callback is executed,
@@ -684,8 +691,10 @@
                         }
                     });
         });
-        mRenderer.mIsDragging = false;
-        mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging);
+        if (mRenderer != null) {
+            mRenderer.mIsDragging = false;
+            mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging);
+        }
     }
 
     /**
@@ -1090,13 +1099,14 @@
         @NonNull
         private final SurfaceControl mDividerSurface;
         @NonNull
+        private final SurfaceControl mDividerLineSurface;
+        @NonNull
         private final WindowlessWindowManager mWindowlessWindowManager;
         @NonNull
         private final SurfaceControlViewHost mViewHost;
         @NonNull
         private final FrameLayout mDividerLayout;
-        @NonNull
-        private final View mDividerLine;
+        @Nullable
         private View mDragHandle;
         @NonNull
         private final View.OnTouchListener mListener;
@@ -1115,7 +1125,10 @@
             mProperties = properties;
             mListener = listener;
 
-            mDividerSurface = createChildSurface("DividerSurface", true /* visible */);
+            mDividerSurface = createChildSurface(
+                    mProperties.mDecorSurface, "DividerSurface", true /* visible */);
+            mDividerLineSurface = createChildSurface(
+                    mDividerSurface, "DividerLineSurface", true /* visible */);
             mWindowlessWindowManager = new WindowlessWindowManager(
                     mProperties.mConfiguration,
                     mDividerSurface,
@@ -1127,7 +1140,6 @@
                     context, displayManager.getDisplay(mProperties.mDisplayId),
                     mWindowlessWindowManager, "DividerContainer");
             mDividerLayout = new FrameLayout(context);
-            mDividerLine = new View(context);
 
             update();
         }
@@ -1220,6 +1232,7 @@
                 dividerSurfacePosition = mDividerPosition;
             }
 
+            // Update the divider surface position relative to the decor surface
             if (mProperties.mIsVerticalSplit) {
                 t.setPosition(mDividerSurface, dividerSurfacePosition, 0.0f);
                 t.setWindowCrop(mDividerSurface, mDividerSurfaceWidthPx, taskBounds.height());
@@ -1228,10 +1241,24 @@
                 t.setWindowCrop(mDividerSurface, taskBounds.width(), mDividerSurfaceWidthPx);
             }
 
-            // Update divider line position in the surface
+            // Update divider line surface position relative to the divider surface
             final int offset = mDividerPosition - dividerSurfacePosition;
-            mDividerLine.setX(mProperties.mIsVerticalSplit ? offset : 0);
-            mDividerLine.setY(mProperties.mIsVerticalSplit ? 0 : offset);
+            if (mProperties.mIsVerticalSplit) {
+                t.setPosition(mDividerLineSurface, offset, 0);
+                t.setWindowCrop(mDividerLineSurface,
+                        mProperties.mDividerWidthPx, taskBounds.height());
+            } else {
+                t.setPosition(mDividerLineSurface, 0, offset);
+                t.setWindowCrop(mDividerLineSurface,
+                        taskBounds.width(), mProperties.mDividerWidthPx);
+            }
+
+            // Update divider line surface visibility and color.
+            // If a container is fully expanded, the divider line is invisible unless dragging.
+            final boolean isDividerLineVisible = !mProperties.mIsDraggableExpandType || mIsDragging;
+            t.setVisibility(mDividerLineSurface, isDividerLineVisible);
+            t.setColor(mDividerLineSurface, colorToFloatArray(
+                    Color.valueOf(mProperties.mDividerAttributes.getDividerColor())));
 
             if (mIsDragging) {
                 updateVeils(t);
@@ -1277,21 +1304,6 @@
          */
         private void updateDivider(@NonNull SurfaceControl.Transaction t) {
             mDividerLayout.removeAllViews();
-            mDividerLayout.addView(mDividerLine);
-            if (mProperties.mIsDraggableExpandType && !mIsDragging) {
-                // If a container is fully expanded, the divider overlays on the expanded container.
-                mDividerLine.setBackgroundColor(Color.TRANSPARENT);
-            } else {
-                mDividerLine.setBackgroundColor(mProperties.mDividerAttributes.getDividerColor());
-            }
-            final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
-            mDividerLine.setLayoutParams(
-                    mProperties.mIsVerticalSplit
-                            ? new FrameLayout.LayoutParams(
-                                    mProperties.mDividerWidthPx, taskBounds.height())
-                            : new FrameLayout.LayoutParams(
-                                    taskBounds.width(), mProperties.mDividerWidthPx)
-            );
             if (mProperties.mDividerAttributes.getDividerType()
                     == DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
                 createVeils();
@@ -1345,10 +1357,11 @@
         }
 
         @NonNull
-        private SurfaceControl createChildSurface(@NonNull String name, boolean visible) {
+        private SurfaceControl createChildSurface(
+                @NonNull SurfaceControl parent, @NonNull String name, boolean visible) {
             final Rect bounds = mProperties.mConfiguration.windowConfiguration.getBounds();
             return new SurfaceControl.Builder()
-                    .setParent(mProperties.mDecorSurface)
+                    .setParent(parent)
                     .setName(name)
                     .setHidden(!visible)
                     .setCallsite("DividerManager.createChildSurface")
@@ -1359,10 +1372,12 @@
 
         private void createVeils() {
             if (mPrimaryVeil == null) {
-                mPrimaryVeil = createChildSurface("DividerPrimaryVeil", false /* visible */);
+                mPrimaryVeil = createChildSurface(
+                        mProperties.mDecorSurface, "DividerPrimaryVeil", false /* visible */);
             }
             if (mSecondaryVeil == null) {
-                mSecondaryVeil = createChildSurface("DividerSecondaryVeil", false /* visible */);
+                mSecondaryVeil = createChildSurface(
+                        mProperties.mDecorSurface, "DividerSecondaryVeil", false /* visible */);
             }
         }
 
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 4e7cfb6..8669af3 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -263,7 +263,7 @@
     <!-- Accessibility text for the caption back button [CHAR LIMIT=NONE] -->
     <string name="back_button_text">Back</string>
     <!-- Accessibility text for the caption handle [CHAR LIMIT=NONE] -->
-    <string name="handle_text">Handle</string>
+    <string name="handle_text">App handle</string>
     <!-- Accessibility text for the handle menu app icon [CHAR LIMIT=NONE] -->
     <string name="app_icon_text">App Icon</string>
     <!-- Accessibility text for the handle fullscreen button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
index d54a6b0..c91567d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -80,6 +80,7 @@
                 outline.setPath(mPath);
             }
         });
+        setContentDescription(getResources().getString(R.string.handle_text));
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index 86cec02..84e32a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -81,6 +81,7 @@
     private boolean mHasNavigationBar = false;
     private boolean mHasStatusBar = false;
     private int mNavBarFrameHeight = 0;
+    private int mTaskbarFrameHeight = 0;
     private boolean mAllowSeamlessRotationDespiteNavBarMoving = false;
     private boolean mNavigationBarCanMove = false;
     private boolean mReverseDefaultRotation = false;
@@ -119,6 +120,7 @@
                 && mNavigationBarCanMove == other.mNavigationBarCanMove
                 && mReverseDefaultRotation == other.mReverseDefaultRotation
                 && mNavBarFrameHeight == other.mNavBarFrameHeight
+                && mTaskbarFrameHeight == other.mTaskbarFrameHeight
                 && Objects.equals(mInsetsState, other.mInsetsState);
     }
 
@@ -126,7 +128,7 @@
     public int hashCode() {
         return Objects.hash(mUiMode, mWidth, mHeight, mCutout, mRotation, mDensityDpi,
                 mNonDecorInsets, mStableInsets, mHasNavigationBar, mHasStatusBar,
-                mNavBarFrameHeight, mAllowSeamlessRotationDespiteNavBarMoving,
+                mNavBarFrameHeight, mTaskbarFrameHeight, mAllowSeamlessRotationDespiteNavBarMoving,
                 mNavigationBarCanMove, mReverseDefaultRotation, mInsetsState);
     }
 
@@ -176,6 +178,7 @@
         mNavigationBarCanMove = dl.mNavigationBarCanMove;
         mReverseDefaultRotation = dl.mReverseDefaultRotation;
         mNavBarFrameHeight = dl.mNavBarFrameHeight;
+        mTaskbarFrameHeight = dl.mTaskbarFrameHeight;
         mNonDecorInsets.set(dl.mNonDecorInsets);
         mStableInsets.set(dl.mStableInsets);
         mInsetsState.set(dl.mInsetsState, true /* copySources */);
@@ -214,7 +217,8 @@
         if (mHasStatusBar) {
             convertNonDecorInsetsToStableInsets(res, mStableInsets, mCutout, mHasStatusBar);
         }
-        mNavBarFrameHeight = getNavigationBarFrameHeight(res, mWidth > mHeight);
+        mNavBarFrameHeight = getNavigationBarFrameHeight(res, /* landscape */ mWidth > mHeight);
+        mTaskbarFrameHeight = SystemBarUtils.getTaskbarHeight(res);
     }
 
     /**
@@ -321,6 +325,17 @@
         outBounds.inset(mStableInsets);
     }
 
+    /** Predicts the calculated stable bounds when in Desktop Mode. */
+    public void getStableBoundsForDesktopMode(Rect outBounds) {
+        getStableBounds(outBounds);
+
+        if (mNavBarFrameHeight != mTaskbarFrameHeight) {
+            // Currently not in pinned taskbar mode, exclude taskbar insets instead of current
+            // navigation insets from bounds.
+            outBounds.bottom = mHeight - mTaskbarFrameHeight;
+        }
+    }
+
     /**
      * Gets navigation bar position for this layout
      * @return Navigation bar position for this layout.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index a52141c5..ba97c832 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -21,6 +21,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.util.RotationUtils.deltaRotation;
 import static android.util.RotationUtils.rotateBounds;
+import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 import static android.view.WindowManager.TRANSIT_CHANGE;
@@ -1183,10 +1184,15 @@
                 ? pipTaskInfo.configuration.windowConfiguration.getBounds()
                 : mPipOrganizer.mAppBounds;
 
+        // Populate the final surface control transactions from PipTransitionAnimator,
+        // display cutout insets is handled in the swipe pip to home animator, empty it out here
+        // to avoid flicker.
+        final Rect savedDisplayCutoutInsets = new Rect(pipTaskInfo.displayCutoutInsets);
+        pipTaskInfo.displayCutoutInsets.setEmpty();
         final PipAnimationController.PipTransitionAnimator animator =
                 mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
                         destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
-                        0 /* startingAngle */, 0 /* rotationDelta */)
+                        0 /* startingAngle */, ROTATION_0 /* rotationDelta */)
                         .setPipTransactionHandler(mTransactionConsumer)
                         .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP);
         // The start state is the end state for swipe-auto-pip.
@@ -1194,6 +1200,7 @@
         animator.applySurfaceControlTransaction(leash, startTransaction,
                 PipAnimationController.FRACTION_END);
         startTransaction.apply();
+        pipTaskInfo.displayCutoutInsets.set(savedDisplayCutoutInsets);
 
         mPipBoundsState.setBounds(destinationBounds);
         final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 9a7ce67..be16207 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -19,6 +19,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.windowingModeToString;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
@@ -593,6 +595,17 @@
                 // through to the windows below so that the app can respond to input events on
                 // their custom content.
                 relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+            } else {
+                if (Flags.enableCaptionCompatInsetForceConsumption()) {
+                    // Force-consume the caption bar insets when the app tries to hide the caption.
+                    // This improves app compatibility of immersive apps.
+                    relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING;
+                }
+            }
+            if (Flags.enableCaptionCompatInsetForceConsumptionAlways()) {
+                // Always force-consume the caption bar insets for maximum app compatibility,
+                // including non-immersive apps that just don't handle caption insets properly.
+                relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
             }
             // Report occluding elements as bounding rects to the insets system so that apps can
             // draw in the empty space in the center:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index a691f59..5f00aa2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -19,7 +19,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
-import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
 import static android.view.WindowInsets.Type.captionBar;
 import static android.view.WindowInsets.Type.mandatorySystemGestures;
 import static android.view.WindowInsets.Type.statusBars;
@@ -54,7 +53,6 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.window.flags.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -375,7 +373,8 @@
         }
 
         final WindowDecorationInsets newInsets = new WindowDecorationInsets(
-                mTaskInfo.token, mOwner, captionInsetsRect, boundingRects);
+                mTaskInfo.token, mOwner, captionInsetsRect, boundingRects,
+                params.mInsetSourceFlags);
         if (!newInsets.equals(mWindowDecorationInsets)) {
             // Add or update this caption as an insets source.
             mWindowDecorationInsets = newInsets;
@@ -660,7 +659,7 @@
         final int captionHeight = loadDimensionPixelSize(mContext.getResources(), captionHeightId);
         final Rect captionInsets = new Rect(0, 0, 0, captionHeight);
         final WindowDecorationInsets newInsets = new WindowDecorationInsets(mTaskInfo.token,
-                mOwner, captionInsets, null /* boundingRets */);
+                mOwner, captionInsets, null /* boundingRets */, 0 /* flags */);
         if (!newInsets.equals(mWindowDecorationInsets)) {
             mWindowDecorationInsets = newInsets;
             mWindowDecorationInsets.addOrUpdate(wct);
@@ -674,6 +673,7 @@
         int mCaptionWidthId;
         final List<OccludingCaptionElement> mOccludingCaptionElements = new ArrayList<>();
         int mInputFeatures;
+        @InsetsSource.Flags int mInsetSourceFlags;
 
         int mShadowRadiusId;
         int mCornerRadius;
@@ -689,6 +689,7 @@
             mCaptionWidthId = Resources.ID_NULL;
             mOccludingCaptionElements.clear();
             mInputFeatures = 0;
+            mInsetSourceFlags = 0;
 
             mShadowRadiusId = Resources.ID_NULL;
             mCornerRadius = 0;
@@ -753,20 +754,20 @@
         private final Binder mOwner;
         private final Rect mFrame;
         private final Rect[] mBoundingRects;
+        private final @InsetsSource.Flags int mFlags;
 
         private WindowDecorationInsets(WindowContainerToken token, Binder owner, Rect frame,
-                Rect[] boundingRects) {
+                Rect[] boundingRects, @InsetsSource.Flags int flags) {
             mToken = token;
             mOwner = owner;
             mFrame = frame;
             mBoundingRects = boundingRects;
+            mFlags = flags;
         }
 
         void addOrUpdate(WindowContainerTransaction wct) {
-            final @InsetsSource.Flags int captionSourceFlags =
-                    Flags.enableCaptionCompatInsetForceConsumption() ? FLAG_FORCE_CONSUMING : 0;
             wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects,
-                    captionSourceFlags);
+                    mFlags);
             wct.addInsetsSource(mToken, mOwner, INDEX, mandatorySystemGestures(), mFrame,
                     mBoundingRects, 0 /* flags */);
         }
@@ -782,12 +783,13 @@
             if (!(o instanceof WindowDecoration.WindowDecorationInsets that)) return false;
             return Objects.equals(mToken, that.mToken) && Objects.equals(mOwner,
                     that.mOwner) && Objects.equals(mFrame, that.mFrame)
-                    && Objects.deepEquals(mBoundingRects, that.mBoundingRects);
+                    && Objects.deepEquals(mBoundingRects, that.mBoundingRects)
+                    && mFlags == that.mFlags;
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(mToken, mOwner, mFrame, Arrays.hashCode(mBoundingRects));
+            return Objects.hash(mToken, mOwner, mFrame, Arrays.hashCode(mBoundingRects), mFlags);
         }
     }
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 4b069f9..9c3016e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -20,6 +20,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
 import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
@@ -411,6 +413,80 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
+    public void updateRelayoutParams_defaultHeader_addsForceConsumingFlag() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance(0);
+        final RelayoutParams relayoutParams = new RelayoutParams();
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams,
+                mTestableContext,
+                taskInfo,
+                /* applyStartTransactionOnDraw= */ true,
+                /* shouldSetTaskPositionAndCrop */ false);
+
+        assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) != 0).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
+    public void updateRelayoutParams_customHeader_noForceConsumptionFlag() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance(
+                APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND);
+        final RelayoutParams relayoutParams = new RelayoutParams();
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams,
+                mTestableContext,
+                taskInfo,
+                /* applyStartTransactionOnDraw= */ true,
+                /* shouldSetTaskPositionAndCrop */ false);
+
+        assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) == 0).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS)
+    public void updateRelayoutParams_header_addsForceConsumingCaptionBar() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        final RelayoutParams relayoutParams = new RelayoutParams();
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams,
+                mTestableContext,
+                taskInfo,
+                /* applyStartTransactionOnDraw= */ true,
+                /* shouldSetTaskPositionAndCrop */ false);
+
+        assertThat(
+                (relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0)
+                .isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS)
+    public void updateRelayoutParams_handle_skipsForceConsumingCaptionBar() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        final RelayoutParams relayoutParams = new RelayoutParams();
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams,
+                mTestableContext,
+                taskInfo,
+                /* applyStartTransactionOnDraw= */ true,
+                /* shouldSetTaskPositionAndCrop */ false);
+
+        assertThat(
+                (relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) == 0)
+                .isTrue();
+    }
+
     @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     public void relayout_fullscreenTask_appliesTransactionImmediately() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 2d1bf14..ca6e03c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
 import static android.view.WindowInsets.Type.captionBar;
 import static android.view.WindowInsets.Type.mandatorySystemGestures;
 import static android.view.WindowInsets.Type.statusBars;
@@ -56,7 +57,6 @@
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.util.DisplayMetrics;
@@ -75,7 +75,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
-import com.android.window.flags.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
@@ -781,8 +780,7 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
-    public void testRelayout_captionInsetForceConsume() {
+    public void testRelayout_captionInsetSourceFlags() {
         final Display defaultDisplay = mock(Display.class);
         doReturn(defaultDisplay).when(mMockDisplayController)
                 .getDisplay(Display.DEFAULT_DISPLAY);
@@ -794,11 +792,14 @@
         final ActivityManager.RunningTaskInfo taskInfo =
                 builder.setToken(token).setBounds(new Rect(0, 0, 1000, 1000)).build();
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+        mRelayoutParams.mInsetSourceFlags =
+                FLAG_FORCE_CONSUMING | FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
         windowDecor.relayout(taskInfo);
 
-        // Caption inset source should be force-consuming.
+        // Caption inset source should add params' flags.
         verify(mMockWindowContainerTransaction).addInsetsSource(eq(token), any(),
-                eq(0) /* index */, eq(captionBar()), any(), any(), eq(FLAG_FORCE_CONSUMING));
+                eq(0) /* index */, eq(captionBar()), any(), any(),
+                eq(FLAG_FORCE_CONSUMING | FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR));
     }
 
     @Test
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index d415700..010c4e8 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -33,9 +33,6 @@
 #define DEBUG_PARCEL 0
 
 static jclass   gBitmap_class;
-static jfieldID gBitmap_nativePtr;
-static jmethodID gBitmap_constructorMethodID;
-static jmethodID gBitmap_reinitMethodID;
 
 namespace android {
 
@@ -183,6 +180,9 @@
 void reinitBitmap(JNIEnv* env, jobject javaBitmap, const SkImageInfo& info,
         bool isPremultiplied)
 {
+    static jmethodID gBitmap_reinitMethodID =
+        GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V");
+
     // The caller needs to have already set the alpha type properly, so the
     // native SkBitmap stays in sync with the Java Bitmap.
     assert_premultiplied(info, isPremultiplied);
@@ -194,6 +194,10 @@
 jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
         int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
         int density) {
+    static jmethodID gBitmap_constructorMethodID =
+        GetMethodIDOrDie(env, gBitmap_class,
+            "<init>", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V");
+
     bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
     bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;
     // The caller needs to have already set the alpha type properly, so the
@@ -232,11 +236,17 @@
 using namespace android;
 using namespace android::bitmap;
 
+static inline jlong getNativePtr(JNIEnv* env, jobject bitmap) {
+    static jfieldID gBitmap_nativePtr =
+        GetFieldIDOrDie(env, gBitmap_class, "mNativePtr", "J");
+    return env->GetLongField(bitmap, gBitmap_nativePtr);
+}
+
 Bitmap* GraphicsJNI::getNativeBitmap(JNIEnv* env, jobject bitmap) {
     SkASSERT(env);
     SkASSERT(bitmap);
     SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class));
-    jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_nativePtr);
+    jlong bitmapHandle = getNativePtr(env, bitmap);
     LocalScopedBitmap localBitmap(bitmapHandle);
     return localBitmap.valid() ? &localBitmap->bitmap() : nullptr;
 }
@@ -246,7 +256,7 @@
     SkASSERT(env);
     SkASSERT(bitmap);
     SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class));
-    jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_nativePtr);
+    jlong bitmapHandle = getNativePtr(env, bitmap);
     LocalScopedBitmap localBitmap(bitmapHandle);
     if (outRowBytes) {
         *outRowBytes = localBitmap->rowBytes();
@@ -1269,9 +1279,6 @@
 int register_android_graphics_Bitmap(JNIEnv* env)
 {
     gBitmap_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Bitmap"));
-    gBitmap_nativePtr = GetFieldIDOrDie(env, gBitmap_class, "mNativePtr", "J");
-    gBitmap_constructorMethodID = GetMethodIDOrDie(env, gBitmap_class, "<init>", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V");
-    gBitmap_reinitMethodID = GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V");
     uirenderer::HardwareBufferHelpers::init();
     return android::RegisterMethodsOrDie(env, "android/graphics/Bitmap", gBitmapMethods,
                                          NELEM(gBitmapMethods));
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 8acaf3b..e575dae 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -5263,6 +5263,8 @@
      *           main thread.)
      */
     public void setCallback(@Nullable /* MediaCodec. */ Callback cb, @Nullable Handler handler) {
+        boolean setCallbackStallFlag =
+            GetFlag(() -> android.media.codec.Flags.setCallbackStall());
         if (cb != null) {
             synchronized (mListenerLock) {
                 EventHandler newHandler = getEventHandlerOn(handler, mCallbackHandler);
@@ -5270,7 +5272,7 @@
                 // even if we were to extend this to be callable dynamically, it must
                 // be called when codec is flushed, so no messages are pending.
                 if (newHandler != mCallbackHandler) {
-                    if (android.media.codec.Flags.setCallbackStall()) {
+                    if (setCallbackStallFlag) {
                         logAndRun(
                                 "[new handler] removeMessages(SET_CALLBACK)",
                                 () -> {
@@ -5289,7 +5291,7 @@
                 }
             }
         } else if (mCallbackHandler != null) {
-            if (android.media.codec.Flags.setCallbackStall()) {
+            if (setCallbackStallFlag) {
                 logAndRun(
                         "[null handler] removeMessages(SET_CALLBACK)",
                         () -> {
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java
index 054c849..c48694c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/EnableZenModeDialog.java
@@ -159,9 +159,9 @@
                         });
 
         if (mCancelIsNeutral) {
-            builder.setNeutralButton(R.string.cancel, null);
+            builder.setNeutralButton(android.R.string.cancel, null);
         } else {
-            builder.setNegativeButton(R.string.cancel, null);
+            builder.setNegativeButton(android.R.string.cancel, null);
         }
 
         View contentView = getContentView();
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 03a2b75..990a2d4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -298,6 +298,10 @@
         return mIsManualDnd;
     }
 
+    public boolean isEnabled() {
+        return mRule.isEnabled();
+    }
+
     public boolean isActive() {
         return mStatus == Status.ENABLED_AND_ACTIVE;
     }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 04d30ed..0b5187c 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -65,6 +65,7 @@
     <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
     <uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
     <uses-permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" />
+    <uses-permission android:name="android.permission.READ_DROPBOX_DATA" />
     <uses-permission android:name="android.permission.READ_LOGS" />
     <uses-permission android:name="android.permission.BRIGHTNESS_SLIDER_USAGE" />
     <uses-permission android:name="android.permission.ACCESS_AMBIENT_LIGHT_STATS" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index eb9d0ab..462db34 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1206,6 +1206,13 @@
 }
 
 flag {
+  name: "hubmode_fullscreen_vertical_swipe"
+  namespace: "systemui"
+  description: "Enables fullscreen vertical swiping in hub mode to bring up and down the bouncer and shade"
+  bug: "340177049"
+}
+
+flag {
    namespace: "systemui"
    name: "remove_update_listener_in_qs_icon_view_impl"
    description: "Remove update listeners in QsIconViewImpl class to avoid memory leak."
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index f73b6cd..9fd30b4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -33,7 +33,7 @@
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
 import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
 
 object Bouncer {
     object Elements {
@@ -56,7 +56,7 @@
 ) : ComposableScene {
     override val key = Scenes.Bouncer
 
-    override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+    override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
         viewModel.destinationScenes
 
     @Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 1ce51af..8c38253 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -98,13 +98,7 @@
                         bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
                     )
 
-                val bottomAreaPlaceable =
-                    bottomAreaMeasurable.measure(
-                        noMinConstraints.copy(
-                            maxHeight =
-                                (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
-                        )
-                    )
+                val bottomAreaPlaceable = bottomAreaMeasurable.measure(noMinConstraints)
 
                 val communalGridPlaceable =
                     communalGridMeasurable.measure(
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 b63b235..fc95754 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
@@ -42,6 +42,7 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.focusable
+import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
@@ -112,6 +113,11 @@
 import androidx.compose.ui.graphics.SolidColor
 import androidx.compose.ui.graphics.drawscope.Stroke
 import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.input.pointer.changedToUp
+import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.boundsInWindow
@@ -138,6 +144,7 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.times
+import androidx.compose.ui.util.fastAll
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.window.layout.WindowMetricsCalculator
@@ -206,13 +213,51 @@
         ScrollOnUpdatedLiveContentEffect(communalContent, gridState)
     }
 
+    val nestedScrollConnection = remember {
+        object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                // Begin tracking nested scrolling
+                viewModel.onNestedScrolling()
+                return super.onPreScroll(available, source)
+            }
+        }
+    }
+
     Box(
         modifier =
             modifier
                 .semantics { testTagsAsResourceId = true }
                 .testTag(COMMUNAL_HUB_TEST_TAG)
                 .fillMaxSize()
+                .nestedScroll(nestedScrollConnection)
                 .pointerInput(gridState, contentOffset, contentListState) {
+                    awaitPointerEventScope {
+                        while (true) {
+                            var event = awaitFirstDown(requireUnconsumed = false)
+                            // Reset touch on first event.
+                            viewModel.onResetTouchState()
+
+                            // Process down event in case it's consumed immediately
+                            if (event.isConsumed) {
+                                viewModel.onHubTouchConsumed()
+                            }
+
+                            do {
+                                var event = awaitPointerEvent()
+                                for (change in event.changes) {
+                                    if (change.isConsumed) {
+                                        // Signal touch consumption on any consumed event.
+                                        viewModel.onHubTouchConsumed()
+                                    }
+                                }
+                            } while (
+                                !event.changes.fastAll {
+                                    it.changedToUp() || it.changedToUpIgnoreConsumed()
+                                }
+                            )
+                        }
+                    }
+
                     // If not in edit mode, don't allow selecting items.
                     if (!viewModel.isEditMode) return@pointerInput
                     observeTaps { offset ->
@@ -793,7 +838,7 @@
             onClick = onClick,
             colors =
                 ButtonDefaults.outlinedButtonColors(
-                    contentColor = colors.primary,
+                    contentColor = colors.onPrimaryContainer,
                 ),
             border = BorderStroke(width = 2.0.dp, color = colors.primary),
             contentPadding = Dimensions.ButtonPadding,
@@ -857,7 +902,7 @@
 /** Creates an empty card used to highlight a particular spot on the grid. */
 @Composable
 fun HighlightedItem(modifier: Modifier = Modifier, alpha: Float = 1.0f) {
-    val brush = SolidColor(LocalAndroidColorScheme.current.primaryFixed)
+    val brush = SolidColor(LocalAndroidColorScheme.current.primary)
     Box(
         modifier =
             // drawBehind lets us draw outside the bounds of the widgets so that we don't need to
@@ -995,6 +1040,11 @@
         modifier =
             modifier
                 .then(selectableModifier)
+                .thenIf(!viewModel.isEditMode && !model.inQuietMode) {
+                    Modifier.pointerInput(Unit) {
+                        observeTaps { viewModel.onTapWidget(model.componentName, model.priority) }
+                    }
+                }
                 .thenIf(!viewModel.isEditMode && model.inQuietMode) {
                     Modifier.pointerInput(Unit) {
                         // consume tap to prevent the child view from triggering interactions with
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
index 94018bb..e0fc340 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
@@ -30,8 +30,8 @@
 import com.android.systemui.scene.ui.composable.ComposableScene
 import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
 /** The communal scene shows glanceable hub when the device is locked and docked. */
@@ -45,7 +45,7 @@
 ) : ComposableScene {
     override val key = Scenes.Communal
 
-    override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+    override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
         MutableStateFlow<Map<UserAction, UserActionResult>>(
                 mapOf(
                     Swipe(SwipeDirection.Right) to UserActionResult(Scenes.Lockscreen),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index c241f9c..b077e18 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -30,7 +30,7 @@
 import com.android.systemui.scene.ui.composable.ComposableScene
 import dagger.Lazy
 import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
 
 /** The lock screen scene shows when the device is locked. */
 @SysUISingleton
@@ -42,7 +42,7 @@
 ) : ComposableScene {
     override val key = Scenes.Lockscreen
 
-    override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+    override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
         viewModel.destinationScenes
 
     @Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 7400711..c01039d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -94,7 +94,7 @@
                             with(topAreaSection) {
                                 DefaultClockLayout(
                                     modifier =
-                                        Modifier.graphicsLayer {
+                                        Modifier.fillMaxWidth(0.5f).graphicsLayer {
                                             translationX = unfoldTranslations.start
                                         }
                                 )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
index 86639fa..b40bccb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -98,7 +98,7 @@
     ) {
         MovableElement(
             key = IndicationAreaElementKey,
-            modifier = modifier.shortcutPadding(),
+            modifier = modifier.indicationAreaPadding(),
         ) {
             content {
                 IndicationArea(
@@ -210,6 +210,11 @@
             )
             .padding(bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset))
     }
+
+    @Composable
+    private fun Modifier.indicationAreaPadding(): Modifier {
+        return this.padding(bottom = dimensionResource(R.dimen.keyguard_indication_margin_bottom))
+    }
 }
 
 private val StartButtonElementKey = ElementKey("StartButton")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
index db98bc8f..7159def 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
@@ -44,7 +44,7 @@
 import dagger.Lazy
 import java.util.Optional
 import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
 
 @SysUISingleton
 class NotificationsShadeScene
@@ -64,7 +64,7 @@
 
     override val key = Scenes.NotificationsShade
 
-    override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+    override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
         sceneViewModel.destinationScenes
 
     @Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 3cf8e70..2800eee 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -69,6 +69,8 @@
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.animateSceneDpAsState
 import com.android.compose.animation.scene.animateSceneFloatAsState
 import com.android.compose.modifiers.thenIf
@@ -79,7 +81,6 @@
 import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.media.controls.ui.composable.MediaCarousel
 import com.android.systemui.media.controls.ui.controller.MediaCarouselController
 import com.android.systemui.media.controls.ui.view.MediaHost
@@ -109,16 +110,13 @@
 import javax.inject.Inject
 import javax.inject.Named
 import kotlin.math.roundToInt
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.Flow
 
 /** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */
 @SysUISingleton
 class QuickSettingsScene
 @Inject
 constructor(
-    @Application private val applicationScope: CoroutineScope,
     private val shadeSession: SaveableSession,
     private val notificationStackScrollView: Lazy<NotificationScrollView>,
     private val viewModel: QuickSettingsSceneViewModel,
@@ -131,12 +129,8 @@
 ) : ComposableScene {
     override val key = Scenes.QuickSettings
 
-    override val destinationScenes =
-        viewModel.destinationScenes.stateIn(
-            scope = applicationScope,
-            started = SharingStarted.WhileSubscribed(),
-            initialValue = emptyMap(),
-        )
+    override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+        viewModel.destinationScenes
 
     @Composable
     override fun SceneScope.Content(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
index 422c9f6..f6d1283 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
@@ -60,7 +60,7 @@
 import dagger.Lazy
 import java.util.Optional
 import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
 
 @SysUISingleton
 class QuickSettingsShadeScene
@@ -76,7 +76,7 @@
 
     override val key = Scenes.QuickSettingsShade
 
-    override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+    override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
         viewModel.destinationScenes
 
     @Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index a44041a..a9ddf84 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -36,7 +36,7 @@
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import dagger.Lazy
 import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
 
 /**
  * "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any
@@ -52,7 +52,7 @@
 ) : ComposableScene {
     override val key = Scenes.Gone
 
-    override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+    override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
         viewModel.destinationScenes
 
     @Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 9c9e6c6..d5874d1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalComposeUiApi::class)
-
 package com.android.systemui.scene.ui.composable
 
 import androidx.compose.foundation.layout.Box
@@ -23,14 +21,13 @@
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.getValue
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.mutableStateMapOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Alignment
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
@@ -40,6 +37,7 @@
 import com.android.systemui.ribbon.ui.composable.BottomRightCornerRibbon
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import kotlinx.coroutines.flow.collectLatest
 
 /**
  * Renders a container of a collection of "scenes" that the user can switch between using certain
@@ -62,19 +60,20 @@
 fun SceneContainer(
     viewModel: SceneContainerViewModel,
     sceneByKey: Map<SceneKey, ComposableScene>,
+    initialSceneKey: SceneKey,
     dataSourceDelegator: SceneDataSourceDelegator,
     modifier: Modifier = Modifier,
 ) {
     val coroutineScope = rememberCoroutineScope()
-    val currentSceneKey: SceneKey by viewModel.currentScene.collectAsStateWithLifecycle()
     val state: MutableSceneTransitionLayoutState = remember {
         MutableSceneTransitionLayoutState(
-            initialScene = currentSceneKey,
+            initialScene = initialSceneKey,
             canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
             transitions = SceneContainerTransitions,
             enableInterruptions = false,
         )
     }
+    val currentSceneKey = state.transitionState.currentScene
 
     DisposableEffect(state) {
         val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope)
@@ -87,19 +86,32 @@
         onDispose { viewModel.setTransitionState(null) }
     }
 
-    val userActionsBySceneKey: Map<SceneKey, Map<UserAction, UserActionResult>> =
-        sceneByKey.values.associate { scene ->
-            val userActions by scene.destinationScenes.collectAsStateWithLifecycle(emptyMap())
-            val resolvedUserActions = viewModel.resolveSceneFamilies(userActions)
-            scene.key to resolvedUserActions
+    val userActionsBySceneKey: MutableMap<SceneKey, Map<UserAction, UserActionResult>> = remember {
+        mutableStateMapOf()
+    }
+    LaunchedEffect(currentSceneKey) {
+        try {
+            sceneByKey[currentSceneKey]?.destinationScenes?.collectLatest { userActions ->
+                userActionsBySceneKey[currentSceneKey] = viewModel.resolveSceneFamilies(userActions)
+            }
+        } finally {
+            userActionsBySceneKey[currentSceneKey] = emptyMap()
         }
+    }
 
     Box(
         modifier = Modifier.fillMaxSize(),
     ) {
         SceneTransitionLayout(state = state, modifier = modifier.fillMaxSize()) {
             sceneByKey.forEach { (sceneKey, composableScene) ->
-                scene(key = sceneKey, userActions = checkNotNull(userActionsBySceneKey[sceneKey])) {
+                scene(
+                    key = sceneKey,
+                    userActions = userActionsBySceneKey.getOrDefault(sceneKey, emptyMap())
+                ) {
+                    // Activate the scene.
+                    LaunchedEffect(composableScene) { composableScene.activate() }
+
+                    // Render the scene.
                     with(composableScene) {
                         this@scene.Content(
                             modifier = Modifier.element(sceneKey.rootElementKey).fillMaxSize(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 09414a9..21e3431 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -109,7 +109,7 @@
 import javax.inject.Inject
 import javax.inject.Named
 import kotlin.math.roundToInt
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
 
 object Shade {
     object Elements {
@@ -153,7 +153,11 @@
 
     override val key = Scenes.Shade
 
-    override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+    override suspend fun activate() {
+        viewModel.activate()
+    }
+
+    override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
         viewModel.destinationScenes
 
     @Composable
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
new file mode 100644
index 0000000..4850085
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.ambient.touch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.ValueAnimator;
+import android.content.pm.UserInfo;
+import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.view.GestureDetector.OnGestureListener;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.Flags;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.ambient.touch.scrim.ScrimController;
+import com.android.systemui.ambient.touch.scrim.ScrimManager;
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.FakeUserTracker;
+import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+@DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
+public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase {
+    private KosmosJavaAdapter mKosmos;
+
+    @Mock
+    CentralSurfaces mCentralSurfaces;
+
+    @Mock
+    ScrimManager mScrimManager;
+
+    @Mock
+    ScrimController mScrimController;
+
+    @Mock
+    NotificationShadeWindowController mNotificationShadeWindowController;
+
+    @Mock
+    FlingAnimationUtils mFlingAnimationUtils;
+
+    @Mock
+    FlingAnimationUtils mFlingAnimationUtilsClosing;
+
+    @Mock
+    TouchHandler.TouchSession mTouchSession;
+
+    BouncerSwipeTouchHandler mTouchHandler;
+
+    @Mock
+    BouncerSwipeTouchHandler.ValueAnimatorCreator mValueAnimatorCreator;
+
+    @Mock
+    ValueAnimator mValueAnimator;
+
+    @Mock
+    BouncerSwipeTouchHandler.VelocityTrackerFactory mVelocityTrackerFactory;
+
+    @Mock
+    VelocityTracker mVelocityTracker;
+
+    @Mock
+    UiEventLogger mUiEventLogger;
+
+    @Mock
+    LockPatternUtils mLockPatternUtils;
+
+    @Mock
+    ActivityStarter mActivityStarter;
+
+    @Mock
+    CommunalViewModel mCommunalViewModel;
+
+    FakeUserTracker mUserTracker;
+
+    private static final float TOUCH_REGION = .3f;
+    private static final float MIN_BOUNCER_HEIGHT = .05f;
+
+    private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100);
+    private static final UserInfo CURRENT_USER_INFO = new UserInfo(
+            10,
+            /* name= */ "user10",
+            /* flags= */ 0
+    );
+
+    @Before
+    public void setup() {
+        mKosmos = new KosmosJavaAdapter(this);
+        MockitoAnnotations.initMocks(this);
+        mUserTracker = new FakeUserTracker();
+        mTouchHandler = new BouncerSwipeTouchHandler(
+                mKosmos.getTestScope(),
+                mScrimManager,
+                Optional.of(mCentralSurfaces),
+                mNotificationShadeWindowController,
+                mValueAnimatorCreator,
+                mVelocityTrackerFactory,
+                mLockPatternUtils,
+                mUserTracker,
+                mCommunalViewModel,
+                mFlingAnimationUtils,
+                mFlingAnimationUtilsClosing,
+                TOUCH_REGION,
+                MIN_BOUNCER_HEIGHT,
+                mUiEventLogger,
+                mActivityStarter);
+
+        when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
+        when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
+        when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker);
+        when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
+        when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
+        when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(true);
+
+        mUserTracker.set(Collections.singletonList(CURRENT_USER_INFO), 0);
+    }
+
+    /**
+     * Ensures expansion does not happen for full vertical swipes when touch is not available.
+     */
+    @Test
+    public void testFullSwipe_notInitiatedWhenNotAvailable() {
+        mTouchHandler.onGlanceableTouchAvailable(false);
+        mTouchHandler.onSessionStart(mTouchSession);
+        ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+                ArgumentCaptor.forClass(OnGestureListener.class);
+        verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+
+        // A touch within range at the bottom of the screen should trigger listening
+        assertThat(gestureListenerCaptor.getValue()
+                .onScroll(Mockito.mock(MotionEvent.class),
+                        Mockito.mock(MotionEvent.class),
+                        1,
+                        2)).isFalse();
+    }
+
+    /**
+     * Ensures expansion only happens for full vertical swipes when touch is available.
+     */
+    @Test
+    public void testFullSwipe_initiatedWhenAvailable() {
+        mTouchHandler.onGlanceableTouchAvailable(true);
+        mTouchHandler.onSessionStart(mTouchSession);
+        ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+                ArgumentCaptor.forClass(OnGestureListener.class);
+        verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+
+        // A touch within range at the bottom of the screen should trigger listening
+        assertThat(gestureListenerCaptor.getValue()
+                .onScroll(Mockito.mock(MotionEvent.class),
+                        Mockito.mock(MotionEvent.class),
+                        1,
+                        2)).isTrue();
+    }
+
+    @Test
+    public void testFullSwipe_motionUpResetsTouchState() {
+        mTouchHandler.onGlanceableTouchAvailable(true);
+        mTouchHandler.onSessionStart(mTouchSession);
+        ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+                ArgumentCaptor.forClass(OnGestureListener.class);
+        ArgumentCaptor<InputChannelCompat.InputEventListener> inputListenerCaptor =
+                ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+        verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+        verify(mTouchSession).registerInputListener(inputListenerCaptor.capture());
+
+        // A touch within range at the bottom of the screen should trigger listening
+        assertThat(gestureListenerCaptor.getValue()
+                .onScroll(Mockito.mock(MotionEvent.class),
+                        Mockito.mock(MotionEvent.class),
+                        1,
+                        2)).isTrue();
+
+        MotionEvent upEvent = Mockito.mock(MotionEvent.class);
+        when(upEvent.getAction()).thenReturn(MotionEvent.ACTION_UP);
+        inputListenerCaptor.getValue().onInputEvent(upEvent);
+        verify(mCommunalViewModel).onResetTouchState();
+    }
+
+    @Test
+    public void testFullSwipe_motionCancelResetsTouchState() {
+        mTouchHandler.onGlanceableTouchAvailable(true);
+        mTouchHandler.onSessionStart(mTouchSession);
+        ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+                ArgumentCaptor.forClass(OnGestureListener.class);
+        ArgumentCaptor<InputChannelCompat.InputEventListener> inputListenerCaptor =
+                ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+        verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+        verify(mTouchSession).registerInputListener(inputListenerCaptor.capture());
+
+        // A touch within range at the bottom of the screen should trigger listening
+        assertThat(gestureListenerCaptor.getValue()
+                .onScroll(Mockito.mock(MotionEvent.class),
+                        Mockito.mock(MotionEvent.class),
+                        1,
+                        2)).isTrue();
+
+        MotionEvent upEvent = Mockito.mock(MotionEvent.class);
+        when(upEvent.getAction()).thenReturn(MotionEvent.ACTION_CANCEL);
+        inputListenerCaptor.getValue().onInputEvent(upEvent);
+        verify(mCommunalViewModel).onResetTouchState();
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
index 7ebc224..0e98b84 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
@@ -50,6 +50,8 @@
 import com.android.systemui.ambient.touch.scrim.ScrimController;
 import com.android.systemui.ambient.touch.scrim.ScrimManager;
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.FakeUserTracker;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
@@ -72,7 +74,9 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
 public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
+    private KosmosJavaAdapter mKosmos;
     @Mock
     CentralSurfaces mCentralSurfaces;
 
@@ -120,6 +124,9 @@
     @Mock
     Region mRegion;
 
+    @Mock
+    CommunalViewModel mCommunalViewModel;
+
     @Captor
     ArgumentCaptor<Rect> mRectCaptor;
 
@@ -139,9 +146,11 @@
 
     @Before
     public void setup() {
+        mKosmos = new KosmosJavaAdapter(this);
         MockitoAnnotations.initMocks(this);
         mUserTracker = new FakeUserTracker();
         mTouchHandler = new BouncerSwipeTouchHandler(
+                mKosmos.getTestScope(),
                 mScrimManager,
                 Optional.of(mCentralSurfaces),
                 mNotificationShadeWindowController,
@@ -149,6 +158,7 @@
                 mVelocityTrackerFactory,
                 mLockPatternUtils,
                 mUserTracker,
+                mCommunalViewModel,
                 mFlingAnimationUtils,
                 mFlingAnimationUtilsClosing,
                 TOUCH_REGION,
@@ -201,7 +211,6 @@
                         2)).isTrue();
     }
 
-
     /**
      * Ensures expansion only happens when touch down happens in valid part of the screen.
      */
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
index 7fd9ce2..204d4b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
@@ -26,8 +26,10 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.ambient.touch.TouchHandler.TouchSession
 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
 import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shared.system.InputChannelCompat
 import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -50,11 +52,11 @@
 @RunWith(AndroidJUnit4::class)
 class ShadeTouchHandlerTest : SysuiTestCase() {
     private var kosmos = testKosmos()
-
     private var mCentralSurfaces = mock<CentralSurfaces>()
     private var mShadeViewController = mock<ShadeViewController>()
     private var mDreamManager = mock<DreamManager>()
     private var mTouchSession = mock<TouchSession>()
+    private var communalViewModel = mock<CommunalViewModel>()
 
     private lateinit var mTouchHandler: ShadeTouchHandler
 
@@ -65,9 +67,11 @@
     fun setup() {
         mTouchHandler =
             ShadeTouchHandler(
+                kosmos.testScope,
                 Optional.of(mCentralSurfaces),
                 mShadeViewController,
                 mDreamManager,
+                communalViewModel,
                 kosmos.communalSettingsInteractor,
                 TOUCH_HEIGHT
             )
@@ -75,6 +79,7 @@
 
     // Verifies that a swipe down in the gesture region is captured by the shade touch handler.
     @Test
+    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
     fun testSwipeDown_captured() {
         val captured = swipe(Direction.DOWN)
         Truth.assertThat(captured).isTrue()
@@ -82,6 +87,7 @@
 
     // Verifies that a swipe in the upward direction is not captured.
     @Test
+    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
     fun testSwipeUp_notCaptured() {
         val captured = swipe(Direction.UP)
 
@@ -91,6 +97,7 @@
 
     // Verifies that a swipe down forwards captured touches to central surfaces for handling.
     @Test
+    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
     @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
     fun testSwipeDown_communalEnabled_sentToCentralSurfaces() {
         kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -103,7 +110,7 @@
 
     // Verifies that a swipe down forwards captured touches to the shade view for handling.
     @Test
-    @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+    @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
     fun testSwipeDown_communalDisabled_sentToShadeView() {
         swipe(Direction.DOWN)
 
@@ -114,6 +121,7 @@
     // Verifies that a swipe down while dreaming forwards captured touches to the shade view for
     // handling.
     @Test
+    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
     fun testSwipeDown_dreaming_sentToShadeView() {
         whenever(mDreamManager.isDreaming).thenReturn(true)
         swipe(Direction.DOWN)
@@ -124,6 +132,7 @@
 
     // Verifies that a swipe up is not forwarded to central surfaces.
     @Test
+    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
     @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
     fun testSwipeUp_communalEnabled_touchesNotSent() {
         kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -137,7 +146,7 @@
 
     // Verifies that a swipe up is not forwarded to the shade view.
     @Test
-    @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+    @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
     fun testSwipeUp_communalDisabled_touchesNotSent() {
         swipe(Direction.UP)
 
@@ -147,6 +156,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
     fun testCancelMotionEvent_popsTouchSession() {
         swipe(Direction.DOWN)
         val event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0)
@@ -154,6 +164,60 @@
         verify(mTouchSession).pop()
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+    fun testFullVerticalSwipe_initiatedWhenAvailable() {
+        // Indicate touches are available
+        mTouchHandler.onGlanceableTouchAvailable(true)
+
+        // Verify swipe is handled
+        val captured = swipe(Direction.DOWN)
+        Truth.assertThat(captured).isTrue()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+    fun testFullVerticalSwipe_notInitiatedWhenNotAvailable() {
+        // Indicate touches aren't available
+        mTouchHandler.onGlanceableTouchAvailable(false)
+
+        // Verify swipe is not handled
+        val captured = swipe(Direction.DOWN)
+        Truth.assertThat(captured).isFalse()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+    fun testFullVerticalSwipe_resetsTouchStateOnUp() {
+        // Indicate touches are available
+        mTouchHandler.onGlanceableTouchAvailable(true)
+
+        // Verify swipe is handled
+        swipe(Direction.DOWN)
+
+        val upEvent: MotionEvent = mock()
+        whenever(upEvent.action).thenReturn(MotionEvent.ACTION_UP)
+        mInputListenerCaptor.lastValue.onInputEvent(upEvent)
+
+        verify(communalViewModel).onResetTouchState()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+    fun testFullVerticalSwipe_resetsTouchStateOnCancel() {
+        // Indicate touches are available
+        mTouchHandler.onGlanceableTouchAvailable(true)
+
+        // Verify swipe is handled
+        swipe(Direction.DOWN)
+
+        val upEvent: MotionEvent = mock()
+        whenever(upEvent.action).thenReturn(MotionEvent.ACTION_CANCEL)
+        mInputListenerCaptor.lastValue.onInputEvent(upEvent)
+
+        verify(communalViewModel).onResetTouchState()
+    }
+
     /**
      * Simulates a swipe in the given direction and returns true if the touch was intercepted by the
      * touch handler's gesture listener.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index c28cf34..a09189e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -372,6 +372,7 @@
         nonAuxiliarySubtypes: Int,
     ): InputMethodModel {
         return InputMethodModel(
+            userId = UUID.randomUUID().mostSignificantBits.toInt(),
             imeId = UUID.randomUUID().toString(),
             subtypes =
                 List(auxiliarySubtypes + nonAuxiliarySubtypes) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt
index def8698..28ad269 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt
@@ -22,10 +22,13 @@
 import com.android.compose.animation.scene.SceneKey
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -53,18 +56,21 @@
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
 
-    private lateinit var communalInteractor: CommunalInteractor
+    private lateinit var communalSceneInteractor: CommunalSceneInteractor
+    private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var underTest: CommunalLoggerStartable
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        communalInteractor = kosmos.communalInteractor
+        communalSceneInteractor = kosmos.communalSceneInteractor
+        keyguardRepository = kosmos.fakeKeyguardRepository
 
         underTest =
             CommunalLoggerStartable(
                 testScope.backgroundScope,
-                communalInteractor,
+                communalSceneInteractor,
+                kosmos.keyguardInteractor,
                 uiEventLogger,
             )
         underTest.start()
@@ -73,10 +79,13 @@
     @Test
     fun transitionStateLogging_enterCommunalHub() =
         testScope.runTest {
+            // Not dreaming
+            keyguardRepository.setDreamingWithOverlay(false)
+
             // Transition state is default (non-communal)
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Default))
-            communalInteractor.setTransitionState(transitionState)
+            communalSceneInteractor.setTransitionState(transitionState)
             runCurrent()
 
             // Verify nothing is logged from the default state
@@ -99,12 +108,15 @@
         }
 
     @Test
-    fun transitionStateLogging_enterCommunalHub_canceled() =
+    fun transitionStateLogging_cancelEnteringCommunalHub() =
         testScope.runTest {
+            // Not dreaming
+            keyguardRepository.setDreamingWithOverlay(false)
+
             // Transition state is default (non-communal)
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Default))
-            communalInteractor.setTransitionState(transitionState)
+            communalSceneInteractor.setTransitionState(transitionState)
             runCurrent()
 
             // Verify nothing is logged from the default state
@@ -132,10 +144,13 @@
     @Test
     fun transitionStateLogging_exitCommunalHub() =
         testScope.runTest {
+            // Not dreaming
+            keyguardRepository.setDreamingWithOverlay(false)
+
             // Transition state is communal
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Communal))
-            communalInteractor.setTransitionState(transitionState)
+            communalSceneInteractor.setTransitionState(transitionState)
             runCurrent()
 
             // Verify SHOWN is logged when it's the default state
@@ -158,12 +173,15 @@
         }
 
     @Test
-    fun transitionStateLogging_exitCommunalHub_canceled() =
+    fun transitionStateLogging_cancelExitingCommunalHub() =
         testScope.runTest {
+            // Not dreaming
+            keyguardRepository.setDreamingWithOverlay(false)
+
             // Transition state is communal
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Communal))
-            communalInteractor.setTransitionState(transitionState)
+            communalSceneInteractor.setTransitionState(transitionState)
             runCurrent()
 
             // Clear the initial SHOWN event from the logger
@@ -188,6 +206,136 @@
             verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
         }
 
+    @Test
+    fun transitionStateLogging_dreaming_enterCommunalHub() =
+        testScope.runTest {
+            // Dreaming
+            keyguardRepository.setDreamingWithOverlay(true)
+
+            // Transition state is default (non-communal)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Default))
+            communalSceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // Verify nothing is logged from the default state
+            verify(uiEventLogger, never()).log(any())
+
+            // Start transition to communal
+            transitionState.value = transition(to = CommunalScenes.Communal)
+            runCurrent()
+
+            // Verify UiEvent logged
+            verify(uiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_START)
+
+            // Finish transition to communal
+            transitionState.value = idle(CommunalScenes.Communal)
+            runCurrent()
+
+            // Verify UiEvent logged
+            verify(uiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_FINISH)
+            verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+        }
+
+    @Test
+    fun transitionStateLogging_dreaming_cancelEnteringCommunalHub() =
+        testScope.runTest {
+            // Dreaming
+            keyguardRepository.setDreamingWithOverlay(true)
+
+            // Transition state is default (non-communal)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Default))
+            communalSceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // Verify nothing is logged from the default state
+            verify(uiEventLogger, never()).log(any())
+
+            // Start transition to communal
+            transitionState.value = transition(to = CommunalScenes.Communal)
+            runCurrent()
+
+            // Verify UiEvent logged
+            verify(uiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_START)
+
+            // Cancel the transition
+            transitionState.value = idle(CommunalScenes.Default)
+            runCurrent()
+
+            // Verify UiEvent logged
+            verify(uiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_CANCEL)
+
+            // Verify neither SHOWN nor GONE is logged
+            verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+            verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
+        }
+
+    @Test
+    fun transitionStateLogging_dreaming_exitCommunalHub() =
+        testScope.runTest {
+            // Dreaming
+            keyguardRepository.setDreamingWithOverlay(true)
+
+            // Transition state is communal
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Communal))
+            communalSceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // Verify SHOWN is logged when it's the default state
+            verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+
+            // Start transition from communal
+            transitionState.value = transition(from = CommunalScenes.Communal)
+            runCurrent()
+
+            // Verify UiEvent logged
+            verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_START)
+
+            // Finish transition to communal
+            transitionState.value = idle(CommunalScenes.Default)
+            runCurrent()
+
+            // Verify UiEvent logged
+            verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_FINISH)
+            verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
+        }
+
+    @Test
+    fun transitionStateLogging_dreaming_cancelExitingCommunalHub() =
+        testScope.runTest {
+            // Dreaming
+            keyguardRepository.setDreamingWithOverlay(true)
+
+            // Transition state is communal
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(idle(CommunalScenes.Communal))
+            communalSceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // Clear the initial SHOWN event from the logger
+            clearInvocations(uiEventLogger)
+
+            // Start transition from communal
+            transitionState.value = transition(from = CommunalScenes.Communal)
+            runCurrent()
+
+            // Verify UiEvent logged
+            verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_START)
+
+            // Cancel the transition
+            transitionState.value = idle(CommunalScenes.Communal)
+            runCurrent()
+
+            // Verify UiEvent logged
+            verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_CANCEL)
+
+            // Verify neither SHOWN nor GONE is logged
+            verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_SHOWN)
+            verify(uiEventLogger, never()).log(CommunalUiEvent.COMMUNAL_HUB_GONE)
+        }
+
     private fun transition(
         from: SceneKey = CommunalScenes.Default,
         to: SceneKey = CommunalScenes.Default,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
index 537ca03..82918a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
@@ -94,6 +94,30 @@
     }
 
     @Test
+    fun logTapWidget_componentNotLoggable_doNotLog() {
+        underTest.logTapWidget(
+            componentName = "com.yellow.package/my_test_widget",
+            rank = 2,
+        )
+        verify(statsLogProxy, never())
+            .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt())
+    }
+
+    @Test
+    fun logTapWidget_componentLoggable_logRemoveEvent() {
+        underTest.logTapWidget(
+            componentName = "com.red.package/my_test_widget",
+            rank = 2,
+        )
+        verify(statsLogProxy)
+            .writeCommunalHubWidgetEventReported(
+                SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__TAP,
+                "com.red.package/my_test_widget",
+                2,
+            )
+    }
+
+    @Test
     fun logWidgetsSnapshot_logOnlyLoggableComponents() {
         val statsEvents = mutableListOf<StatsEvent>()
         underTest.logWidgetsSnapshot(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index d862a21..7a41bc6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal.view.viewmodel
 
+import android.content.ComponentName
 import android.content.pm.UserInfo
 import android.platform.test.flag.junit.FlagsParameterization
 import android.provider.Settings
@@ -41,6 +42,7 @@
 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.communal.domain.interactor.communalTutorialInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.log.CommunalMetricsLogger
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
@@ -107,6 +109,7 @@
 @RunWith(ParameterizedAndroidJunit4::class)
 class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     @Mock private lateinit var mediaHost: MediaHost
+    @Mock private lateinit var metricsLogger: CommunalMetricsLogger
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
@@ -170,7 +173,8 @@
                 kosmos.communalTutorialInteractor,
                 kosmos.shadeInteractor,
                 mediaHost,
-                logcatLogBuffer("CommunalViewModelTest")
+                logcatLogBuffer("CommunalViewModelTest"),
+                metricsLogger,
             )
     }
 
@@ -746,6 +750,23 @@
         verify(communalInteractor).setScrollPosition(eq(index), eq(offset))
     }
 
+    @Test
+    fun onTapWidget_logEvent() {
+        underTest.onTapWidget(ComponentName("test_pkg", "test_cls"), priority = 10)
+        verify(metricsLogger).logTapWidget("test_pkg/test_cls", rank = 10)
+    }
+
+    @Test
+    fun glanceableTouchAvailable_availableWhenNestedScrollingWithoutConsumption() =
+        testScope.runTest {
+            val touchAvailable by collectLastValue(underTest.glanceableTouchAvailable)
+            assertThat(touchAvailable).isTrue()
+            underTest.onHubTouchConsumed()
+            assertThat(touchAvailable).isFalse()
+            underTest.onNestedScrolling()
+            assertThat(touchAvailable).isTrue()
+        }
+
     private suspend fun setIsMainUser(isMainUser: Boolean) {
         val user = if (isMainUser) MAIN_USER_INFO else SECONDARY_USER_INFO
         with(userRepository) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 60c9bb0..4c24ce2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -54,6 +54,7 @@
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.complication.ComplicationHostViewController
 import com.android.systemui.complication.ComplicationLayoutEngine
@@ -660,6 +661,7 @@
             verify(mDreamOverlayCallback).onRedirectWake(true)
             client.onWakeRequested()
             verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), isNull())
+            verify(mUiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt
index 857cdce..274880b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt
@@ -56,9 +56,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        whenever(inputMethodManager.getEnabledInputMethodSubtypeList(eq(null), anyBoolean()))
-            .thenReturn(listOf())
-
         underTest =
             InputMethodRepositoryImpl(
                 backgroundDispatcher = kosmos.testDispatcher,
@@ -71,10 +68,16 @@
         testScope.runTest {
             whenever(inputMethodManager.getEnabledInputMethodListAsUser(eq(USER_HANDLE)))
                 .thenReturn(listOf())
-            whenever(inputMethodManager.getEnabledInputMethodSubtypeList(any(), anyBoolean()))
+            whenever(
+                    inputMethodManager.getEnabledInputMethodSubtypeListAsUser(
+                        any(),
+                        anyBoolean(),
+                        eq(USER_HANDLE)
+                    )
+                )
                 .thenReturn(listOf())
 
-            assertThat(underTest.enabledInputMethods(USER_ID, fetchSubtypes = true).count())
+            assertThat(underTest.enabledInputMethods(USER_HANDLE, fetchSubtypes = true).count())
                 .isEqualTo(0)
         }
 
@@ -83,11 +86,20 @@
         testScope.runTest {
             val subtypeId = 123
             val isAuxiliary = true
+            val selectedImiId = "imiId"
+            val selectedImi = mock<InputMethodInfo>()
+            whenever(selectedImi.id).thenReturn(selectedImiId)
+            whenever(inputMethodManager.getCurrentInputMethodInfoAsUser(eq(USER_HANDLE)))
+                .thenReturn(selectedImi)
             whenever(inputMethodManager.getEnabledInputMethodListAsUser(eq(USER_HANDLE)))
-                .thenReturn(listOf(mock<InputMethodInfo>()))
-            whenever(inputMethodManager.getEnabledInputMethodSubtypeList(any(), anyBoolean()))
-                .thenReturn(listOf())
-            whenever(inputMethodManager.getEnabledInputMethodSubtypeList(eq(null), anyBoolean()))
+                .thenReturn(listOf(selectedImi))
+            whenever(
+                    inputMethodManager.getEnabledInputMethodSubtypeListAsUser(
+                        eq(selectedImiId),
+                        anyBoolean(),
+                        eq(USER_HANDLE)
+                    )
+                )
                 .thenReturn(
                     listOf(
                         InputMethodSubtype.InputMethodSubtypeBuilder()
@@ -97,7 +109,7 @@
                     )
                 )
 
-            val result = underTest.selectedInputMethodSubtypes()
+            val result = underTest.selectedInputMethodSubtypes(USER_HANDLE)
             assertThat(result).hasSize(1)
             assertThat(result.first().subtypeId).isEqualTo(subtypeId)
             assertThat(result.first().isAuxiliary).isEqualTo(isAuxiliary)
@@ -108,7 +120,7 @@
         testScope.runTest {
             val displayId = 7
 
-            underTest.showInputMethodPicker(displayId, /* showAuxiliarySubtypes = */ true)
+            underTest.showInputMethodPicker(displayId, /* showAuxiliarySubtypes= */ true)
 
             verify(inputMethodManager)
                 .showInputMethodPickerFromSystem(
@@ -118,7 +130,6 @@
         }
 
     companion object {
-        private const val USER_ID = 100
-        private val USER_HANDLE = UserHandle.of(USER_ID)
+        private val USER_HANDLE = UserHandle.of(100)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
index d23ff2a..8e6de2f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
@@ -143,6 +143,7 @@
         nonAuxiliarySubtypes: Int,
     ): InputMethodModel {
         return InputMethodModel(
+            userId = UUID.randomUUID().mostSignificantBits.toInt(),
             imeId = UUID.randomUUID().toString(),
             subtypes =
                 List(auxiliarySubtypes + nonAuxiliarySubtypes) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
index 65aa825..7424320 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
@@ -32,11 +32,13 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
 import com.android.systemui.communal.shared.model.CommunalScenes
@@ -49,13 +51,13 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.testKosmos
-import kotlin.test.Test
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.spy
@@ -108,6 +110,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun testShowWhenLockedActivity_noLongerOnTop_transitionsToGlanceableHub_ifIdleOnCommunal() =
         testScope.runTest {
             kosmos.fakeCommunalSceneRepository.setTransitionState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 3db9ef1..bca83f0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -257,7 +257,6 @@
 
     private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
         return LockscreenSceneViewModel(
-            applicationScope = testScope.backgroundScope,
             deviceEntryInteractor = kosmos.deviceEntryInteractor,
             communalInteractor = kosmos.communalInteractor,
             touchHandling =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
index a67e7c6..d5c9102 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
@@ -21,68 +21,64 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Expandable
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
 import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
-import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
 import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
 import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
-import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
+import com.google.common.truth.Truth
+import kotlin.coroutines.EmptyCoroutineContext
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.kotlin.eq
-import org.mockito.kotlin.mock
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
 import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @EnableFlags(android.app.Flags.FLAG_MODES_UI)
 class ModesTileUserActionInteractorTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
-    private val inputHandler = kosmos.qsTileIntentUserInputHandler
-    private val mockDialogDelegate = kosmos.mockModesDialogDelegate
+    private val inputHandler = FakeQSTileIntentUserInputHandler()
 
-    private val underTest =
-        ModesTileUserActionInteractor(
-            inputHandler,
-            mockDialogDelegate,
-        )
+    @Mock private lateinit var dialogTransitionAnimator: DialogTransitionAnimator
+    @Mock private lateinit var dialogDelegate: ModesDialogDelegate
+    @Mock private lateinit var mockDialog: SystemUIDialog
 
-    @Test
-    fun handleClick_active() = runTest {
-        val expandable = mock<Expandable>()
-        underTest.handleInput(
-            QSTileInputTestKtx.click(data = ModesTileModel(true), expandable = expandable))
+    private lateinit var underTest: ModesTileUserActionInteractor
 
-        verify(mockDialogDelegate).showDialog(eq(expandable))
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(dialogDelegate.createDialog()).thenReturn(mockDialog)
+
+        underTest =
+            ModesTileUserActionInteractor(
+                EmptyCoroutineContext,
+                inputHandler,
+                dialogTransitionAnimator,
+                dialogDelegate,
+            )
     }
 
     @Test
-    fun handleClick_inactive() = runTest {
-        val expandable = mock<Expandable>()
-        underTest.handleInput(
-            QSTileInputTestKtx.click(data = ModesTileModel(false), expandable = expandable))
+    fun handleClick() = runTest {
+        underTest.handleInput(QSTileInputTestKtx.click(ModesTileModel(false)))
 
-        verify(mockDialogDelegate).showDialog(eq(expandable))
+        verify(mockDialog).show()
     }
 
     @Test
-    fun handleLongClick_active() = runTest {
-        underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(true)))
-
-        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
-            assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
-        }
-    }
-
-    @Test
-    fun handleLongClick_inactive() = runTest {
+    fun handleLongClick() = runTest {
         underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(false)))
 
         QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
-            assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
+            Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
         }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 9249621..3ca802e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -93,7 +93,6 @@
         sceneContainerStartable.start()
         underTest =
             QuickSettingsSceneViewModel(
-                applicationScope = testScope.backgroundScope,
                 brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
                 shadeHeaderViewModel = kosmos.shadeHeaderViewModel,
                 qsSceneAdapter = qsFlexiglassAdapter,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 39b3662..228d61a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -143,7 +143,6 @@
 
     private val lockscreenSceneViewModel by lazy {
         LockscreenSceneViewModel(
-            applicationScope = testScope.backgroundScope,
             deviceEntryInteractor = deviceEntryInteractor,
             communalInteractor = communalInteractor,
             touchHandling =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
index 0ab6a82..a4992e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
@@ -53,7 +53,6 @@
     fun setUp() {
         underTest =
             GoneSceneViewModel(
-                applicationScope = testScope.backgroundScope,
                 shadeInteractor = kosmos.shadeInteractor,
             )
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index da22c6d..343b6bd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -25,6 +25,7 @@
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.SwipeDirection
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activatable.activateIn
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
@@ -59,6 +60,7 @@
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -78,6 +80,11 @@
 
     private val underTest: ShadeSceneViewModel by lazy { kosmos.shadeSceneViewModel }
 
+    @Before
+    fun setUp() {
+        underTest.activateIn(testScope)
+    }
+
     @Test
     fun upTransitionSceneKey_deviceLocked_lockScreen() =
         testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
index 8810ade..7b87aeb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
@@ -20,7 +20,6 @@
 import android.app.Notification
 import android.app.NotificationManager.IMPORTANCE_DEFAULT
 import android.app.NotificationManager.IMPORTANCE_LOW
-import android.os.UserHandle
 import android.platform.test.annotations.EnableFlags
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -475,20 +474,6 @@
 
         val collectionListener: NotifCollectionListener =
             argumentCaptor { verify(notifPipeline).addCollectionListener(capture()) }.lastValue
-
-        var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
-            get() =
-                fakeSettings.getIntForUser(
-                    Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-                    UserHandle.USER_CURRENT,
-                ) == 1
-            set(value) {
-                fakeSettings.putIntForUser(
-                    Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-                    if (value) 1 else 2,
-                    UserHandle.USER_CURRENT,
-                )
-            }
     }
 
     companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
index 7e9f437..3fd9c21 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
@@ -18,21 +18,23 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.app.Notification
-import android.os.UserHandle
 import android.platform.test.flag.junit.FlagsParameterization
 import android.provider.Settings
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
+import com.android.systemui.dump.dumpManager
 import com.android.systemui.flags.andSceneContainer
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.plugins.statusbar.statusBarStateController
 import com.android.systemui.scene.data.repository.Idle
 import com.android.systemui.scene.data.repository.setTransition
 import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -43,12 +45,15 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.lockScreenShowOnlyUnseenNotificationsSetting
+import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import com.android.systemui.statusbar.policy.headsUpManager
+import com.android.systemui.testKosmos
 import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineScope
@@ -73,13 +78,23 @@
 @RunWith(ParameterizedAndroidJunit4::class)
 class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() {
 
-    private val kosmos = Kosmos()
+    private val kosmos =
+        testKosmos().apply {
+            testDispatcher = UnconfinedTestDispatcher()
+            statusBarStateController = mock()
+            fakeSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
+        }
 
-    private val headsUpManager: HeadsUpManager = mock()
-    private val keyguardRepository = FakeKeyguardRepository()
-    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val keyguardRepository
+        get() = kosmos.fakeKeyguardRepository
+
+    private val keyguardTransitionRepository
+        get() = kosmos.fakeKeyguardTransitionRepository
+
+    private val statusBarStateController
+        get() = kosmos.statusBarStateController
+
     private val notifPipeline: NotifPipeline = mock()
-    private val statusBarStateController: StatusBarStateController = mock()
 
     init {
         mSetFlagsRule.setFlagsParameterization(flags)
@@ -253,7 +268,7 @@
             collectionListener.onEntryAdded(fakeEntry)
 
             // GIVEN: The setting for filtering unseen notifications is disabled
-            showOnlyUnseenNotifsOnKeyguardSetting = false
+            kosmos.lockScreenShowOnlyUnseenNotificationsSetting = false
 
             // GIVEN: The pipeline has registered the unseen filter for invalidation
             val invalidationListener: Pluggable.PluggableListener<NotifFilter> = mock()
@@ -267,7 +282,7 @@
             assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
 
             // WHEN: The secure setting is changed
-            showOnlyUnseenNotifsOnKeyguardSetting = true
+            kosmos.lockScreenShowOnlyUnseenNotificationsSetting = true
 
             // THEN: The pipeline is invalidated
             verify(invalidationListener).onPluggableInvalidated(same(unseenFilter), any())
@@ -608,35 +623,25 @@
     private fun runKeyguardCoordinatorTest(
         testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
     ) {
-        val testDispatcher = UnconfinedTestDispatcher()
-        val testScope = TestScope(testDispatcher)
-        val fakeSettings =
-            FakeSettings().apply {
-                putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
-            }
-        val seenNotificationsInteractor =
-            SeenNotificationsInteractor(ActiveNotificationListRepository())
         val keyguardCoordinator =
             OriginalUnseenKeyguardCoordinator(
-                testDispatcher,
-                mock<DumpManager>(),
-                headsUpManager,
-                keyguardRepository,
-                kosmos.keyguardTransitionInteractor,
-                KeyguardCoordinatorLogger(logcatLogBuffer()),
-                testScope.backgroundScope,
-                fakeSettings,
-                seenNotificationsInteractor,
-                statusBarStateController,
+                dumpManager = kosmos.dumpManager,
+                headsUpManager = kosmos.headsUpManager,
+                keyguardRepository = kosmos.keyguardRepository,
+                keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+                logger = KeyguardCoordinatorLogger(logcatLogBuffer()),
+                scope = kosmos.testScope.backgroundScope,
+                seenNotificationsInteractor = kosmos.seenNotificationsInteractor,
+                statusBarStateController = kosmos.statusBarStateController,
                 sceneInteractor = kosmos.sceneInteractor,
             )
         keyguardCoordinator.attach(notifPipeline)
-        testScope.runTest {
+        kosmos.testScope.runTest {
             KeyguardCoordinatorTestScope(
                     keyguardCoordinator,
-                    testScope,
-                    seenNotificationsInteractor,
-                    fakeSettings,
+                    kosmos.testScope,
+                    kosmos.seenNotificationsInteractor,
+                    kosmos.fakeSettings,
                 )
                 .testBlock()
         }
@@ -658,21 +663,8 @@
             argumentCaptor { verify(notifPipeline).addCollectionListener(capture()) }.lastValue
 
         val onHeadsUpChangedListener: OnHeadsUpChangedListener
-            get() = argumentCaptor { verify(headsUpManager).addListener(capture()) }.lastValue
-
-        var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
             get() =
-                fakeSettings.getIntForUser(
-                    Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-                    UserHandle.USER_CURRENT,
-                ) == 1
-            set(value) {
-                fakeSettings.putIntForUser(
-                    Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-                    if (value) 1 else 2,
-                    UserHandle.USER_CURRENT,
-                )
-            }
+                argumentCaptor { verify(kosmos.headsUpManager).addListener(capture()) }.lastValue
     }
 
     companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
new file mode 100644
index 0000000..2159b86
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class SeenNotificationsInteractorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val underTest
+        get() = kosmos.seenNotificationsInteractor
+
+    @Test
+    fun testNoFilteredOutSeenNotifications() = runTest {
+        val hasFilteredOutSeenNotifications by
+            collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+        underTest.setHasFilteredOutSeenNotifications(false)
+
+        assertThat(hasFilteredOutSeenNotifications).isFalse()
+    }
+
+    @Test
+    fun testHasFilteredOutSeenNotifications() = runTest {
+        val hasFilteredOutSeenNotifications by
+            collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+        underTest.setHasFilteredOutSeenNotifications(true)
+
+        assertThat(hasFilteredOutSeenNotifications).isTrue()
+    }
+
+    @Test
+    @EnableFlags(NotificationMinimalismPrototype.FLAG_NAME)
+    fun topOngoingAndUnseenNotification() = runTest {
+        val entry1 = NotificationEntryBuilder().setTag("entry1").build()
+        val entry2 = NotificationEntryBuilder().setTag("entry2").build()
+
+        underTest.setTopOngoingNotification(null)
+        underTest.setTopUnseenNotification(null)
+
+        assertThat(underTest.isTopOngoingNotification(entry1)).isFalse()
+        assertThat(underTest.isTopOngoingNotification(entry2)).isFalse()
+        assertThat(underTest.isTopUnseenNotification(entry1)).isFalse()
+        assertThat(underTest.isTopUnseenNotification(entry2)).isFalse()
+
+        underTest.setTopOngoingNotification(entry1)
+        underTest.setTopUnseenNotification(entry2)
+
+        assertThat(underTest.isTopOngoingNotification(entry1)).isTrue()
+        assertThat(underTest.isTopOngoingNotification(entry2)).isFalse()
+        assertThat(underTest.isTopUnseenNotification(entry1)).isFalse()
+        assertThat(underTest.isTopUnseenNotification(entry2)).isTrue()
+    }
+
+    fun testShowOnlyUnseenNotifsOnKeyguardSetting() = runTest {
+        val settingEnabled by
+            collectLastValue(underTest.isLockScreenShowOnlyUnseenNotificationsEnabled())
+
+        kosmos.lockScreenShowOnlyUnseenNotificationsSetting = false
+        testScheduler.runCurrent()
+        assertThat(settingEnabled).isFalse()
+
+        kosmos.lockScreenShowOnlyUnseenNotificationsSetting = true
+        testScheduler.runCurrent()
+        assertThat(settingEnabled).isTrue()
+    }
+}
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 8f9da3b..9a862fc 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
@@ -60,6 +60,7 @@
     // For creating TestableHeadsUpManager
     @Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
     private val mUiEventLoggerFake = UiEventLoggerFake()
+    @Mock private lateinit var mHeadsUpManagerLogger: HeadsUpManagerLogger
 
     @Mock private lateinit var mBgHandler: Handler
 
@@ -82,7 +83,8 @@
 
         // Initialize AvalancheController and TestableHeadsUpManager during setUp instead of
         // declaration, where mocks are null
-        mAvalancheController = AvalancheController(dumpManager, mUiEventLoggerFake, mBgHandler)
+        mAvalancheController = AvalancheController(dumpManager, mUiEventLoggerFake,
+                mHeadsUpManagerLogger, 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 df07b44..9005ae3 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
@@ -81,6 +81,7 @@
     static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
 
     private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
+
     private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
     @Mock private Handler mBgHandler;
     @Mock private DumpManager dumpManager;
@@ -149,7 +150,8 @@
     @Override
     public void SysuiSetup() throws Exception {
         super.SysuiSetup();
-        mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake, mBgHandler);
+        mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake, mLogger,
+                mBgHandler);
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
index b91bde4..7a6838a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -179,7 +179,8 @@
         mContext
             .getOrCreateTestableResources()
             .addOverride(R.integer.ambient_notification_extension_time, 500)
-        mAvalancheController = AvalancheController(dumpManager, mUiEventLogger, mBgHandler)
+        mAvalancheController = AvalancheController(dumpManager, mUiEventLogger,
+                mHeadsUpManagerLogger, mBgHandler)
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
deleted file mode 100644
index bf0a39b..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.statusbar.policy.ui.dialog
-
-import android.app.Dialog
-import android.content.Intent
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityTransitionAnimator
-import com.android.systemui.animation.mockActivityTransitionAnimatorController
-import com.android.systemui.animation.mockDialogTransitionAnimator
-import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.kosmos.mainCoroutineContext
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.runOnMainThreadAndWaitForIdleSync
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.statusbar.phone.systemUIDialogFactory
-import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.any
-import org.mockito.kotlin.eq
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.verify
-import org.mockito.kotlin.whenever
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class ModesDialogDelegateTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-    private val activityStarter = kosmos.activityStarter
-    private val mockDialogTransitionAnimator = kosmos.mockDialogTransitionAnimator
-    private val mockAnimationController = kosmos.mockActivityTransitionAnimatorController
-    private lateinit var underTest: ModesDialogDelegate
-
-    @Before
-    fun setup() {
-        whenever(
-                mockDialogTransitionAnimator.createActivityTransitionController(
-                    any<SystemUIDialog>(),
-                    eq(null)
-                )
-            )
-            .thenReturn(mockAnimationController)
-
-        underTest =
-            ModesDialogDelegate(
-                kosmos.systemUIDialogFactory,
-                mockDialogTransitionAnimator,
-                activityStarter,
-                { kosmos.modesDialogViewModel },
-                kosmos.mainCoroutineContext,
-            )
-    }
-
-    @Test
-    fun launchFromDialog_whenDialogNotOpen() {
-        val intent: Intent = mock()
-
-        runOnMainThreadAndWaitForIdleSync { underTest.launchFromDialog(intent) }
-
-        verify(activityStarter)
-            .startActivity(eq(intent), eq(true), eq<ActivityTransitionAnimator.Controller?>(null))
-    }
-
-    @Test
-    fun launchFromDialog_whenDialogOpen() =
-        testScope.runTest {
-            val intent: Intent = mock()
-            lateinit var dialog: Dialog
-
-            runOnMainThreadAndWaitForIdleSync {
-                kosmos.applicationCoroutineScope.launch { dialog = underTest.showDialog() }
-                runCurrent()
-                underTest.launchFromDialog(intent)
-            }
-
-            verify(mockDialogTransitionAnimator)
-                .createActivityTransitionController(any<Dialog>(), eq(null))
-            verify(activityStarter).startActivity(eq(intent), eq(true), eq(mockAnimationController))
-
-            runOnMainThreadAndWaitForIdleSync { dialog.dismiss() }
-        }
-
-    @Test
-    fun dismiss_clearsDialogReference() {
-        val dialog = runOnMainThreadAndWaitForIdleSync { underTest.createDialog() }
-
-        assertThat(underTest.currentDialog).isEqualTo(dialog)
-
-        runOnMainThreadAndWaitForIdleSync {
-            dialog.show()
-            dialog.dismiss()
-        }
-
-        assertThat(underTest.currentDialog).isNull()
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index 349b62e..fdfc7f1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -18,8 +18,6 @@
 
 package com.android.systemui.statusbar.policy.ui.dialog.viewmodel
 
-import android.content.Intent
-import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.notification.modes.TestModeBuilder
@@ -29,7 +27,6 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
 import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
-import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,21 +34,16 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.clearInvocations
-import org.mockito.kotlin.argumentCaptor
-import org.mockito.kotlin.verify
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class ModesDialogViewModelTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val repository = kosmos.fakeZenModeRepository
-    private val interactor = kosmos.zenModeInteractor
-    private val mockDialogDelegate = kosmos.mockModesDialogDelegate
+    val repository = kosmos.fakeZenModeRepository
+    val interactor = kosmos.zenModeInteractor
 
-    private val underTest =
-        ModesDialogViewModel(context, interactor, kosmos.testDispatcher, mockDialogDelegate)
+    val underTest = ModesDialogViewModel(context, interactor, kosmos.testDispatcher)
 
     @Test
     fun tiles_filtersOutDisabledModes() =
@@ -72,8 +64,7 @@
                         .setEnabled(false)
                         .setManualInvocationAllowed(true)
                         .build(),
-                )
-            )
+                ))
             runCurrent()
 
             assertThat(tiles?.size).isEqualTo(2)
@@ -117,8 +108,7 @@
                         .setActive(false)
                         .setManualInvocationAllowed(false)
                         .build(),
-                )
-            )
+                ))
             runCurrent()
 
             assertThat(tiles?.size).isEqualTo(3)
@@ -171,56 +161,4 @@
 
             assertThat(tiles?.first()?.enabled).isFalse()
         }
-
-    @Test
-    fun onLongClick_launchesIntent() =
-        testScope.runTest {
-            val tiles by collectLastValue(underTest.tiles)
-            val intentCaptor = argumentCaptor<Intent>()
-
-            val modeId = "id"
-            repository.addModes(
-                listOf(
-                    TestModeBuilder()
-                        .setId(modeId)
-                        .setId("A")
-                        .setActive(true)
-                        .setManualInvocationAllowed(true)
-                        .build(),
-                    TestModeBuilder()
-                        .setId(modeId)
-                        .setId("B")
-                        .setActive(false)
-                        .setManualInvocationAllowed(true)
-                        .build(),
-                )
-            )
-            runCurrent()
-
-            assertThat(tiles?.size).isEqualTo(2)
-
-            // Trigger onLongClick for A
-            tiles?.first()?.onLongClick?.let { it() }
-            runCurrent()
-
-            // Check that it launched the correct intent
-            verify(mockDialogDelegate).launchFromDialog(intentCaptor.capture())
-            var intent = intentCaptor.lastValue
-            assertThat(intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
-            assertThat(intent.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID))
-                .isEqualTo("A")
-
-            clearInvocations(mockDialogDelegate)
-
-            // Trigger onLongClick for B
-            tiles?.last()?.onLongClick?.let { it() }
-            runCurrent()
-
-            // Check that it launched the correct intent
-            verify(mockDialogDelegate).launchFromDialog(intentCaptor.capture())
-            intent = intentCaptor.lastValue
-            assertThat(intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
-            assertThat(intent.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID))
-                .isEqualTo("B")
-        }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 0f11717..1342dd0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -287,7 +287,7 @@
     /**
      * Helper used to receive device state info from {@link DeviceStateManager}.
      */
-    static class DeviceStateHelper implements DeviceStateManager.DeviceStateCallback {
+    public static class DeviceStateHelper implements DeviceStateManager.DeviceStateCallback {
 
         @Nullable
         private final DisplayAddress.Physical mRearDisplayPhysicalAddress;
diff --git a/packages/SystemUI/src/com/android/systemui/activatable/Activatable.kt b/packages/SystemUI/src/com/android/systemui/activatable/Activatable.kt
new file mode 100644
index 0000000..dc2d931
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/activatable/Activatable.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.activatable
+
+/** Defines interface for classes that can be activated to do coroutine work. */
+interface Activatable {
+
+    /**
+     * Activates this object.
+     *
+     * Serves as an entrypoint to kick off coroutine work that the object requires in order to keep
+     * its state fresh and/or perform side-effects.
+     *
+     * The method suspends and doesn't return until all work required by the object is finished. In
+     * most cases, it's expected for the work to remain ongoing forever so this method will forever
+     * suspend its caller until the coroutine that called it is canceled.
+     *
+     * Implementations could follow this pattern:
+     * ```kotlin
+     * override suspend fun activate() {
+     *     coroutineScope {
+     *         launch { ... }
+     *         launch { ... }
+     *         launch { ... }
+     *     }
+     * }
+     * ```
+     *
+     * **Must be invoked** by the owner of the object when the object is to become active.
+     * Similarly, the work must be canceled by the owner when the objects is to be deactivated.
+     *
+     * One way to have a parent call this would be by using a `LaunchedEffect` in Compose:
+     * ```kotlin
+     * @Composable
+     * fun MyUi(activatable: Activatable) {
+     *     LaunchedEffect(activatable) {
+     *         activatable.activate()
+     *     }
+     * }
+     * ```
+     */
+    suspend fun activate()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
deleted file mode 100644
index 636bc5b..0000000
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.ambient.touch;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.util.Log;
-import android.view.GestureDetector;
-import android.view.InputEvent;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.internal.logging.UiEvent;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Flags;
-import com.android.systemui.ambient.touch.dagger.BouncerSwipeModule;
-import com.android.systemui.ambient.touch.scrim.ScrimController;
-import com.android.systemui.ambient.touch.scrim.ScrimManager;
-import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.ShadeExpansionChangeEvent;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.wm.shell.animation.FlingAnimationUtils;
-
-import java.util.Optional;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * Monitor for tracking touches on the DreamOverlay to bring up the bouncer.
- */
-public class BouncerSwipeTouchHandler implements TouchHandler {
-    /**
-     * An interface for creating ValueAnimators.
-     */
-    public interface ValueAnimatorCreator {
-        /**
-         * Creates {@link ValueAnimator}.
-         */
-        ValueAnimator create(float start, float finish);
-    }
-
-    /**
-     * An interface for obtaining VelocityTrackers.
-     */
-    public interface VelocityTrackerFactory {
-        /**
-         * Obtains {@link VelocityTracker}.
-         */
-        VelocityTracker obtain();
-    }
-
-    public static final float FLING_PERCENTAGE_THRESHOLD = 0.5f;
-
-    private static final String TAG = "BouncerSwipeTouchHandler";
-    private final NotificationShadeWindowController mNotificationShadeWindowController;
-    private final LockPatternUtils mLockPatternUtils;
-    private final UserTracker mUserTracker;
-    private final float mBouncerZoneScreenPercentage;
-    private final float mMinBouncerZoneScreenPercentage;
-
-    private final ScrimManager mScrimManager;
-    private ScrimController mCurrentScrimController;
-    private float mCurrentExpansion;
-    private final Optional<CentralSurfaces> mCentralSurfaces;
-
-    private VelocityTracker mVelocityTracker;
-
-    private final FlingAnimationUtils mFlingAnimationUtils;
-    private final FlingAnimationUtils mFlingAnimationUtilsClosing;
-
-    private Boolean mCapture;
-    private Boolean mExpanded;
-
-    private TouchSession mTouchSession;
-
-    private final ValueAnimatorCreator mValueAnimatorCreator;
-
-    private final VelocityTrackerFactory mVelocityTrackerFactory;
-
-    private final UiEventLogger mUiEventLogger;
-
-    private final ActivityStarter mActivityStarter;
-
-    private final ScrimManager.Callback mScrimManagerCallback = new ScrimManager.Callback() {
-        @Override
-        public void onScrimControllerChanged(ScrimController controller) {
-            if (mCurrentScrimController != null) {
-                mCurrentScrimController.reset();
-            }
-
-            mCurrentScrimController = controller;
-        }
-    };
-
-    private final GestureDetector.OnGestureListener mOnGestureListener =
-            new GestureDetector.SimpleOnGestureListener() {
-                @Override
-                public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX,
-                        float distanceY) {
-                    if (mCapture == null) {
-                        if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) {
-                            mCapture = Math.abs(distanceY) > Math.abs(distanceX)
-                                    && distanceY > 0;
-                        } else {
-                            // If the user scrolling favors a vertical direction, begin capturing
-                            // scrolls.
-                            mCapture = Math.abs(distanceY) > Math.abs(distanceX);
-                        }
-                        if (mCapture) {
-                            // reset expanding
-                            mExpanded = false;
-                            // Since the user is dragging the bouncer up, set scrimmed to false.
-                            mCurrentScrimController.show();
-                        }
-                    }
-
-                    if (!mCapture) {
-                        return false;
-                    }
-
-                    // Don't set expansion for downward scroll.
-                    if (e1.getY() < e2.getY()) {
-                        return true;
-                    }
-
-                    if (!mCentralSurfaces.isPresent()) {
-                        return true;
-                    }
-
-                    // If scrolling up and keyguard is not locked, dismiss both keyguard and the
-                    // dream since there's no bouncer to show.
-                    if (e1.getY() > e2.getY()
-                            && !mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
-                        mActivityStarter.executeRunnableDismissingKeyguard(
-                                () -> mCentralSurfaces.get().awakenDreams(),
-                                /* cancelAction= */ null,
-                                /* dismissShade= */ true,
-                                /* afterKeyguardGone= */ true,
-                                /* deferred= */ false);
-                        return true;
-                    }
-
-                    // For consistency, we adopt the expansion definition found in the
-                    // PanelViewController. In this case, expansion refers to the view above the
-                    // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
-                    // is fully hidden at full expansion (1) and fully visible when fully collapsed
-                    // (0).
-                    final float screenTravelPercentage = Math.abs(e1.getY() - e2.getY())
-                            / mTouchSession.getBounds().height();
-                    setPanelExpansion(1 - screenTravelPercentage);
-                    return true;
-                }
-            };
-
-    private void setPanelExpansion(float expansion) {
-        mCurrentExpansion = expansion;
-        ShadeExpansionChangeEvent event =
-                new ShadeExpansionChangeEvent(
-                        /* fraction= */ mCurrentExpansion,
-                        /* expanded= */ mExpanded,
-                        /* tracking= */ true);
-        mCurrentScrimController.expand(event);
-    }
-
-
-    @VisibleForTesting
-    public enum DreamEvent implements UiEventLogger.UiEventEnum {
-        @UiEvent(doc = "The screensaver has been swiped up.")
-        DREAM_SWIPED(988),
-
-        @UiEvent(doc = "The bouncer has become fully visible over dream.")
-        DREAM_BOUNCER_FULLY_VISIBLE(1056);
-
-        private final int mId;
-
-        DreamEvent(int id) {
-            mId = id;
-        }
-
-        @Override
-        public int getId() {
-            return mId;
-        }
-    }
-
-    @Inject
-    public BouncerSwipeTouchHandler(
-            ScrimManager scrimManager,
-            Optional<CentralSurfaces> centralSurfaces,
-            NotificationShadeWindowController notificationShadeWindowController,
-            ValueAnimatorCreator valueAnimatorCreator,
-            VelocityTrackerFactory velocityTrackerFactory,
-            LockPatternUtils lockPatternUtils,
-            UserTracker userTracker,
-            @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
-            FlingAnimationUtils flingAnimationUtils,
-            @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
-            FlingAnimationUtils flingAnimationUtilsClosing,
-            @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage,
-            @Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) float minRegionPercentage,
-            UiEventLogger uiEventLogger,
-            ActivityStarter activityStarter) {
-        mCentralSurfaces = centralSurfaces;
-        mScrimManager = scrimManager;
-        mNotificationShadeWindowController = notificationShadeWindowController;
-        mLockPatternUtils = lockPatternUtils;
-        mUserTracker = userTracker;
-        mBouncerZoneScreenPercentage = swipeRegionPercentage;
-        mMinBouncerZoneScreenPercentage = minRegionPercentage;
-        mFlingAnimationUtils = flingAnimationUtils;
-        mFlingAnimationUtilsClosing = flingAnimationUtilsClosing;
-        mValueAnimatorCreator = valueAnimatorCreator;
-        mVelocityTrackerFactory = velocityTrackerFactory;
-        mUiEventLogger = uiEventLogger;
-        mActivityStarter = activityStarter;
-    }
-
-    @Override
-    public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
-        final int width = bounds.width();
-        final int height = bounds.height();
-        final int minAllowableBottom = Math.round(height * (1 - mMinBouncerZoneScreenPercentage));
-
-        final Rect normalRegion = new Rect(0,
-                Math.round(height * (1 - mBouncerZoneScreenPercentage)),
-                width, height);
-
-        if (exclusionRect != null) {
-            int lowestBottom = Math.min(Math.max(0, exclusionRect.bottom), minAllowableBottom);
-            normalRegion.top = Math.max(normalRegion.top, lowestBottom);
-        }
-        region.union(normalRegion);
-    }
-
-
-    @Override
-    public void onSessionStart(TouchSession session) {
-        mVelocityTracker = mVelocityTrackerFactory.obtain();
-        mTouchSession = session;
-        mVelocityTracker.clear();
-
-        if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
-            mNotificationShadeWindowController.setForcePluginOpen(true, this);
-        }
-
-        mScrimManager.addCallback(mScrimManagerCallback);
-        mCurrentScrimController = mScrimManager.getCurrentController();
-
-        session.registerCallback(() -> {
-            if (mVelocityTracker != null) {
-                mVelocityTracker.recycle();
-                mVelocityTracker = null;
-            }
-            mScrimManager.removeCallback(mScrimManagerCallback);
-            mCapture = null;
-            mTouchSession = null;
-
-            if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
-                mNotificationShadeWindowController.setForcePluginOpen(false, this);
-            }
-        });
-
-        session.registerGestureListener(mOnGestureListener);
-        session.registerInputListener(ev -> onMotionEvent(ev));
-
-    }
-
-    private void onMotionEvent(InputEvent event) {
-        if (!(event instanceof MotionEvent)) {
-            Log.e(TAG, "non MotionEvent received:" + event);
-            return;
-        }
-
-        final MotionEvent motionEvent = (MotionEvent) event;
-
-        switch (motionEvent.getAction()) {
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_UP:
-                mTouchSession.pop();
-                // If we are not capturing any input, there is no need to consider animating to
-                // finish transition.
-                if (mCapture == null || !mCapture) {
-                    break;
-                }
-
-                // We must capture the resulting velocities as resetMonitor() will clear these
-                // values.
-                mVelocityTracker.computeCurrentVelocity(1000);
-                final float verticalVelocity = mVelocityTracker.getYVelocity();
-                final float horizontalVelocity = mVelocityTracker.getXVelocity();
-
-                final float velocityVector =
-                        (float) Math.hypot(horizontalVelocity, verticalVelocity);
-
-                mExpanded = !flingRevealsOverlay(verticalVelocity, velocityVector);
-                final float expansion = mExpanded
-                        ? KeyguardBouncerConstants.EXPANSION_VISIBLE
-                        : KeyguardBouncerConstants.EXPANSION_HIDDEN;
-
-                // Log the swiping up to show Bouncer event.
-                if (expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
-                    mUiEventLogger.log(DreamEvent.DREAM_SWIPED);
-                }
-
-                flingToExpansion(verticalVelocity, expansion);
-                break;
-            default:
-                mVelocityTracker.addMovement(motionEvent);
-                break;
-        }
-    }
-
-    private ValueAnimator createExpansionAnimator(float targetExpansion) {
-        final ValueAnimator animator =
-                mValueAnimatorCreator.create(mCurrentExpansion, targetExpansion);
-        animator.addUpdateListener(
-                animation -> {
-                    float expansionFraction = (float) animation.getAnimatedValue();
-                    setPanelExpansion(expansionFraction);
-                });
-        if (targetExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
-            animator.addListener(
-                    new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            mUiEventLogger.log(DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE);
-                        }
-                    });
-        }
-        return animator;
-    }
-
-    protected boolean flingRevealsOverlay(float velocity, float velocityVector) {
-        // Fully expand the space above the bouncer, if the user has expanded the bouncer less
-        // than halfway or final velocity was positive, indicating a downward direction.
-        if (Math.abs(velocityVector) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
-            return mCurrentExpansion > FLING_PERCENTAGE_THRESHOLD;
-        } else {
-            return velocity > 0;
-        }
-    }
-
-    protected void flingToExpansion(float velocity, float expansion) {
-        if (!mCentralSurfaces.isPresent()) {
-            return;
-        }
-
-        // Don't set expansion if the user doesn't have a pin/password set.
-        if (!mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
-            return;
-        }
-
-        // The animation utils deal in pixel units, rather than expansion height.
-        final float viewHeight = mTouchSession.getBounds().height();
-        final float currentHeight = viewHeight * mCurrentExpansion;
-        final float targetHeight = viewHeight * expansion;
-        final ValueAnimator animator = createExpansionAnimator(expansion);
-        if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) {
-            // Hides the bouncer, i.e., fully expands the space above the bouncer.
-            mFlingAnimationUtilsClosing.apply(animator, currentHeight, targetHeight, velocity,
-                    viewHeight);
-        } else {
-            // Shows the bouncer, i.e., fully collapses the space above the bouncer.
-            mFlingAnimationUtils.apply(
-                    animator, currentHeight, targetHeight, velocity, viewHeight);
-        }
-
-        animator.start();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
new file mode 100644
index 0000000..d5790a4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.ambient.touch
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.Rect
+import android.graphics.Region
+import android.util.Log
+import android.view.GestureDetector
+import android.view.GestureDetector.SimpleOnGestureListener
+import android.view.InputEvent
+import android.view.MotionEvent
+import android.view.VelocityTracker
+import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.Flags
+import com.android.systemui.ambient.touch.TouchHandler.TouchSession
+import com.android.systemui.ambient.touch.dagger.BouncerSwipeModule
+import com.android.systemui.ambient.touch.scrim.ScrimController
+import com.android.systemui.ambient.touch.scrim.ScrimManager
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.wm.shell.animation.FlingAnimationUtils
+import java.util.Optional
+import javax.inject.Inject
+import javax.inject.Named
+import kotlin.math.abs
+import kotlin.math.hypot
+import kotlin.math.max
+import kotlin.math.min
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/** Monitor for tracking touches on the DreamOverlay to bring up the bouncer. */
+class BouncerSwipeTouchHandler
+@Inject
+constructor(
+    scope: CoroutineScope,
+    private val scrimManager: ScrimManager,
+    private val centralSurfaces: Optional<CentralSurfaces>,
+    private val notificationShadeWindowController: NotificationShadeWindowController,
+    private val valueAnimatorCreator: ValueAnimatorCreator,
+    private val velocityTrackerFactory: VelocityTrackerFactory,
+    private val lockPatternUtils: LockPatternUtils,
+    private val userTracker: UserTracker,
+    private val communalViewModel: CommunalViewModel,
+    @param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
+    private val flingAnimationUtils: FlingAnimationUtils,
+    @param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
+    private val flingAnimationUtilsClosing: FlingAnimationUtils,
+    @param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION)
+    private val bouncerZoneScreenPercentage: Float,
+    @param:Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE)
+    private val minBouncerZoneScreenPercentage: Float,
+    private val uiEventLogger: UiEventLogger,
+    private val activityStarter: ActivityStarter
+) : TouchHandler {
+    /** An interface for creating ValueAnimators. */
+    interface ValueAnimatorCreator {
+        /** Creates [ValueAnimator]. */
+        fun create(start: Float, finish: Float): ValueAnimator
+    }
+
+    /** An interface for obtaining VelocityTrackers. */
+    interface VelocityTrackerFactory {
+        /** Obtains [VelocityTracker]. */
+        fun obtain(): VelocityTracker?
+    }
+
+    private var currentScrimController: ScrimController? = null
+    private var currentExpansion = 0f
+    private var velocityTracker: VelocityTracker? = null
+    private var capture: Boolean? = null
+    private var expanded: Boolean = false
+    private var touchSession: TouchSession? = null
+    private val scrimManagerCallback =
+        ScrimManager.Callback { controller ->
+            currentScrimController?.reset()
+
+            currentScrimController = controller
+        }
+
+    /** Determines whether the touch handler should process touches in fullscreen swiping mode */
+    private var touchAvailable = false
+
+    private val onGestureListener: GestureDetector.OnGestureListener =
+        object : SimpleOnGestureListener() {
+            override fun onScroll(
+                e1: MotionEvent?,
+                e2: MotionEvent,
+                distanceX: Float,
+                distanceY: Float
+            ): Boolean {
+                if (capture == null) {
+                    capture =
+                        if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) {
+                            (abs(distanceY.toDouble()) > abs(distanceX.toDouble()) &&
+                                distanceY > 0) &&
+                                if (Flags.hubmodeFullscreenVerticalSwipe()) touchAvailable else true
+                        } else {
+                            // If the user scrolling favors a vertical direction, begin capturing
+                            // scrolls.
+                            abs(distanceY.toDouble()) > abs(distanceX.toDouble())
+                        }
+                    if (capture == true) {
+                        // reset expanding
+                        expanded = false
+                        // Since the user is dragging the bouncer up, set scrimmed to false.
+                        currentScrimController?.show()
+                    }
+                }
+                if (capture != true) {
+                    return false
+                }
+
+                if (!centralSurfaces.isPresent) {
+                    return true
+                }
+
+                e1?.apply outer@{
+                    // Don't set expansion for downward scroll.
+                    if (y < e2.y) {
+                        return true
+                    }
+
+                    // If scrolling up and keyguard is not locked, dismiss both keyguard and the
+                    // dream since there's no bouncer to show.
+                    if (y > e2.y && !lockPatternUtils.isSecure(userTracker.userId)) {
+                        activityStarter.executeRunnableDismissingKeyguard(
+                            { centralSurfaces.get().awakenDreams() },
+                            /* cancelAction= */ null,
+                            /* dismissShade= */ true,
+                            /* afterKeyguardGone= */ true,
+                            /* deferred= */ false
+                        )
+                        return true
+                    }
+
+                    // For consistency, we adopt the expansion definition found in the
+                    // PanelViewController. In this case, expansion refers to the view above the
+                    // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
+                    // is fully hidden at full expansion (1) and fully visible when fully collapsed
+                    // (0).
+                    touchSession?.apply {
+                        val screenTravelPercentage =
+                            (abs((this@outer.y - e2.y).toDouble()) / getBounds().height()).toFloat()
+                        setPanelExpansion(1 - screenTravelPercentage)
+                    }
+                }
+
+                return true
+            }
+        }
+
+    init {
+        if (Flags.hubmodeFullscreenVerticalSwipe()) {
+            scope.launch {
+                communalViewModel.glanceableTouchAvailable.collect {
+                    onGlanceableTouchAvailable(it)
+                }
+            }
+        }
+    }
+
+    @VisibleForTesting
+    fun onGlanceableTouchAvailable(available: Boolean) {
+        touchAvailable = available
+    }
+
+    private fun setPanelExpansion(expansion: Float) {
+        currentExpansion = expansion
+        val event =
+            ShadeExpansionChangeEvent(
+                /* fraction= */ currentExpansion,
+                /* expanded= */ expanded,
+                /* tracking= */ true
+            )
+        currentScrimController?.expand(event)
+    }
+
+    @VisibleForTesting
+    enum class DreamEvent(private val mId: Int) : UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "The screensaver has been swiped up.") DREAM_SWIPED(988),
+        @UiEvent(doc = "The bouncer has become fully visible over dream.")
+        DREAM_BOUNCER_FULLY_VISIBLE(1056);
+
+        override fun getId(): Int {
+            return mId
+        }
+    }
+
+    override fun getTouchInitiationRegion(bounds: Rect, region: Region, exclusionRect: Rect?) {
+        val width = bounds.width()
+        val height = bounds.height()
+        val minAllowableBottom = Math.round(height * (1 - minBouncerZoneScreenPercentage))
+        val normalRegion =
+            Rect(0, Math.round(height * (1 - bouncerZoneScreenPercentage)), width, height)
+
+        if (Flags.hubmodeFullscreenVerticalSwipe()) {
+            region.op(bounds, Region.Op.UNION)
+            exclusionRect?.apply { region.op(this, Region.Op.DIFFERENCE) }
+        }
+
+        if (exclusionRect != null) {
+            val lowestBottom =
+                min(max(0.0, exclusionRect.bottom.toDouble()), minAllowableBottom.toDouble())
+                    .toInt()
+            normalRegion.top = max(normalRegion.top.toDouble(), lowestBottom.toDouble()).toInt()
+        }
+        region.union(normalRegion)
+    }
+
+    override fun onSessionStart(session: TouchSession) {
+        velocityTracker = velocityTrackerFactory.obtain()
+        touchSession = session
+        velocityTracker?.apply { clear() }
+        if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
+            notificationShadeWindowController.setForcePluginOpen(true, this)
+        }
+        scrimManager.addCallback(scrimManagerCallback)
+        currentScrimController = scrimManager.currentController
+        session.registerCallback {
+            velocityTracker?.apply { recycle() }
+            velocityTracker = null
+
+            scrimManager.removeCallback(scrimManagerCallback)
+            capture = null
+            touchSession = null
+            if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
+                notificationShadeWindowController.setForcePluginOpen(false, this)
+            }
+        }
+        session.registerGestureListener(onGestureListener)
+        session.registerInputListener { ev: InputEvent -> onMotionEvent(ev) }
+    }
+
+    private fun onMotionEvent(event: InputEvent) {
+        if (event !is MotionEvent) {
+            Log.e(TAG, "non MotionEvent received:$event")
+            return
+        }
+        val motionEvent = event
+        when (motionEvent.action) {
+            MotionEvent.ACTION_CANCEL,
+            MotionEvent.ACTION_UP -> {
+                if (Flags.hubmodeFullscreenVerticalSwipe() && capture == true) {
+                    communalViewModel.onResetTouchState()
+                }
+                touchSession?.apply { pop() }
+                // If we are not capturing any input, there is no need to consider animating to
+                // finish transition.
+                if (capture == null || !capture!!) {
+                    return
+                }
+
+                // We must capture the resulting velocities as resetMonitor() will clear these
+                // values.
+                velocityTracker!!.computeCurrentVelocity(1000)
+                val verticalVelocity = velocityTracker!!.yVelocity
+                val horizontalVelocity = velocityTracker!!.xVelocity
+                val velocityVector =
+                    hypot(horizontalVelocity.toDouble(), verticalVelocity.toDouble()).toFloat()
+                expanded = !flingRevealsOverlay(verticalVelocity, velocityVector)
+                val expansion =
+                    if (expanded!!) KeyguardBouncerConstants.EXPANSION_VISIBLE
+                    else KeyguardBouncerConstants.EXPANSION_HIDDEN
+
+                // Log the swiping up to show Bouncer event.
+                if (expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
+                    uiEventLogger.log(DreamEvent.DREAM_SWIPED)
+                }
+                flingToExpansion(verticalVelocity, expansion)
+            }
+            else -> velocityTracker!!.addMovement(motionEvent)
+        }
+    }
+
+    private fun createExpansionAnimator(targetExpansion: Float): ValueAnimator {
+        val animator = valueAnimatorCreator.create(currentExpansion, targetExpansion)
+        animator.addUpdateListener { animation: ValueAnimator ->
+            val expansionFraction = animation.animatedValue as Float
+            setPanelExpansion(expansionFraction)
+        }
+        if (targetExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
+            animator.addListener(
+                object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator) {
+                        uiEventLogger.log(DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE)
+                    }
+                }
+            )
+        }
+        return animator
+    }
+
+    protected fun flingRevealsOverlay(velocity: Float, velocityVector: Float): Boolean {
+        // Fully expand the space above the bouncer, if the user has expanded the bouncer less
+        // than halfway or final velocity was positive, indicating a downward direction.
+        return if (abs(velocityVector.toDouble()) < flingAnimationUtils.minVelocityPxPerSecond) {
+            currentExpansion > FLING_PERCENTAGE_THRESHOLD
+        } else {
+            velocity > 0
+        }
+    }
+
+    protected fun flingToExpansion(velocity: Float, expansion: Float) {
+        if (!centralSurfaces.isPresent) {
+            return
+        }
+
+        // Don't set expansion if the user doesn't have a pin/password set.
+        if (!lockPatternUtils.isSecure(userTracker.userId)) {
+            return
+        }
+
+        touchSession?.apply {
+            // The animation utils deal in pixel units, rather than expansion height.
+            val viewHeight = getBounds().height().toFloat()
+            val currentHeight = viewHeight * currentExpansion
+            val targetHeight = viewHeight * expansion
+            val animator = createExpansionAnimator(expansion)
+            if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) {
+                // Hides the bouncer, i.e., fully expands the space above the bouncer.
+                flingAnimationUtilsClosing.apply(
+                    animator,
+                    currentHeight,
+                    targetHeight,
+                    velocity,
+                    viewHeight
+                )
+            } else {
+                // Shows the bouncer, i.e., fully collapses the space above the bouncer.
+                flingAnimationUtils.apply(
+                    animator,
+                    currentHeight,
+                    targetHeight,
+                    velocity,
+                    viewHeight
+                )
+            }
+            animator.start()
+        }
+    }
+
+    companion object {
+        const val FLING_PERCENTAGE_THRESHOLD = 0.5f
+        private const val TAG = "BouncerSwipeTouchHandler"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
deleted file mode 100644
index baca959..0000000
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.ambient.touch;
-
-import static com.android.systemui.ambient.touch.dagger.ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT;
-
-import android.app.DreamManager;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor;
-import com.android.systemui.shade.ShadeViewController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-
-import java.util.Optional;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * {@link ShadeTouchHandler} is responsible for handling swipe down gestures over dream
- * to bring down the shade.
- */
-public class ShadeTouchHandler implements TouchHandler {
-    private final Optional<CentralSurfaces> mSurfaces;
-    private final ShadeViewController mShadeViewController;
-    private final DreamManager mDreamManager;
-    private final int mInitiationHeight;
-    private final CommunalSettingsInteractor
-            mCommunalSettingsInteractor;
-
-    /**
-     * Tracks whether or not we are capturing a given touch. Will be null before and after a touch.
-     */
-    private Boolean mCapture;
-
-    @Inject
-    ShadeTouchHandler(Optional<CentralSurfaces> centralSurfaces,
-            ShadeViewController shadeViewController,
-            DreamManager dreamManager,
-            CommunalSettingsInteractor communalSettingsInteractor,
-            @Named(NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT) int initiationHeight) {
-        mSurfaces = centralSurfaces;
-        mShadeViewController = shadeViewController;
-        mDreamManager = dreamManager;
-        mCommunalSettingsInteractor = communalSettingsInteractor;
-        mInitiationHeight = initiationHeight;
-    }
-
-    @Override
-    public void onSessionStart(TouchSession session) {
-        if (mSurfaces.isEmpty()) {
-            session.pop();
-            return;
-        }
-
-        session.registerCallback(() -> mCapture = null);
-
-        session.registerInputListener(ev -> {
-            if (ev instanceof MotionEvent) {
-                if (mCapture != null && mCapture) {
-                    sendTouchEvent((MotionEvent) ev);
-                }
-                if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP
-                        || ((MotionEvent) ev).getAction() == MotionEvent.ACTION_CANCEL) {
-                    session.pop();
-                }
-            }
-        });
-
-        session.registerGestureListener(new GestureDetector.SimpleOnGestureListener() {
-            @Override
-            public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX,
-                    float distanceY) {
-                if (mCapture == null) {
-                    // Only capture swipes that are going downwards.
-                    mCapture = Math.abs(distanceY) > Math.abs(distanceX) && distanceY < 0;
-                    if (mCapture) {
-                        // Send the initial touches over, as the input listener has already
-                        // processed these touches.
-                        sendTouchEvent(e1);
-                        sendTouchEvent(e2);
-                    }
-                }
-                return mCapture;
-            }
-
-            @Override
-            public boolean onFling(MotionEvent e1, @NonNull MotionEvent e2, float velocityX,
-                    float velocityY) {
-                return mCapture;
-            }
-        });
-    }
-
-    private void sendTouchEvent(MotionEvent event) {
-        if (mCommunalSettingsInteractor.isCommunalFlagEnabled() && !mDreamManager.isDreaming()) {
-            // Send touches to central surfaces only when on the glanceable hub while not dreaming.
-            // While sending touches where while dreaming will open the shade, the shade
-            // while closing if opened then closed in the same gesture.
-            mSurfaces.get().handleExternalShadeWindowTouch(event);
-        } else {
-            // Send touches to the shade view when dreaming.
-            mShadeViewController.handleExternalTouch(event);
-        }
-    }
-
-    @Override
-    public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
-        final Rect outBounds = new Rect(bounds);
-        outBounds.inset(0, 0, 0, outBounds.height() - mInitiationHeight);
-        region.op(outBounds, Region.Op.UNION);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
new file mode 100644
index 0000000..06b41de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.ambient.touch
+
+import android.app.DreamManager
+import android.graphics.Rect
+import android.graphics.Region
+import android.view.GestureDetector.SimpleOnGestureListener
+import android.view.InputEvent
+import android.view.MotionEvent
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Flags
+import com.android.systemui.ambient.touch.TouchHandler.TouchSession
+import com.android.systemui.ambient.touch.dagger.ShadeModule
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import java.util.Optional
+import javax.inject.Inject
+import javax.inject.Named
+import kotlin.math.abs
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * [ShadeTouchHandler] is responsible for handling swipe down gestures over dream to bring down the
+ * shade.
+ */
+class ShadeTouchHandler
+@Inject
+constructor(
+    scope: CoroutineScope,
+    private val surfaces: Optional<CentralSurfaces>,
+    private val shadeViewController: ShadeViewController,
+    private val dreamManager: DreamManager,
+    private val communalViewModel: CommunalViewModel,
+    private val communalSettingsInteractor: CommunalSettingsInteractor,
+    @param:Named(ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT)
+    private val initiationHeight: Int
+) : TouchHandler {
+    /**
+     * Tracks whether or not we are capturing a given touch. Will be null before and after a touch.
+     */
+    private var capture: Boolean? = null
+
+    /** Determines whether the touch handler should process touches in fullscreen swiping mode */
+    private var touchAvailable = false
+
+    init {
+        if (Flags.hubmodeFullscreenVerticalSwipe()) {
+            scope.launch {
+                communalViewModel.glanceableTouchAvailable.collect {
+                    onGlanceableTouchAvailable(it)
+                }
+            }
+        }
+    }
+
+    @VisibleForTesting
+    fun onGlanceableTouchAvailable(available: Boolean) {
+        touchAvailable = available
+    }
+
+    override fun onSessionStart(session: TouchSession) {
+        if (surfaces.isEmpty) {
+            session.pop()
+            return
+        }
+        session.registerCallback { capture = null }
+        session.registerInputListener { ev: InputEvent? ->
+            if (ev is MotionEvent) {
+                if (capture == true) {
+                    sendTouchEvent(ev)
+                }
+                if (ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_CANCEL) {
+                    if (capture == true) {
+                        communalViewModel.onResetTouchState()
+                    }
+                    session.pop()
+                }
+            }
+        }
+        session.registerGestureListener(
+            object : SimpleOnGestureListener() {
+                override fun onScroll(
+                    e1: MotionEvent?,
+                    e2: MotionEvent,
+                    distanceX: Float,
+                    distanceY: Float
+                ): Boolean {
+                    if (capture == null) {
+                        // Only capture swipes that are going downwards.
+                        capture =
+                            abs(distanceY.toDouble()) > abs(distanceX.toDouble()) &&
+                                distanceY < 0 &&
+                                if (Flags.hubmodeFullscreenVerticalSwipe()) touchAvailable else true
+                        if (capture == true) {
+                            // Send the initial touches over, as the input listener has already
+                            // processed these touches.
+                            e1?.apply { sendTouchEvent(this) }
+                            sendTouchEvent(e2)
+                        }
+                    }
+                    return capture == true
+                }
+
+                override fun onFling(
+                    e1: MotionEvent?,
+                    e2: MotionEvent,
+                    velocityX: Float,
+                    velocityY: Float
+                ): Boolean {
+                    return capture == true
+                }
+            }
+        )
+    }
+
+    private fun sendTouchEvent(event: MotionEvent) {
+        if (communalSettingsInteractor.isCommunalFlagEnabled() && !dreamManager.isDreaming) {
+            // Send touches to central surfaces only when on the glanceable hub while not dreaming.
+            // While sending touches where while dreaming will open the shade, the shade
+            // while closing if opened then closed in the same gesture.
+            surfaces.get().handleExternalShadeWindowTouch(event)
+        } else {
+            // Send touches to the shade view when dreaming.
+            shadeViewController.handleExternalTouch(event)
+        }
+    }
+
+    override fun getTouchInitiationRegion(bounds: Rect, region: Region, exclusionRect: Rect?) {
+        // If fullscreen swipe, use entire space minus exclusion region
+        if (Flags.hubmodeFullscreenVerticalSwipe()) {
+            region.op(bounds, Region.Op.UNION)
+
+            exclusionRect?.apply { region.op(this, Region.Op.DIFFERENCE) }
+        }
+
+        val outBounds = Rect(bounds)
+        outBounds.inset(0, 0, 0, outBounds.height() - initiationHeight)
+        region.op(outBounds, Region.Op.UNION)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
index a4924d1..ae21e56 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
@@ -17,12 +17,14 @@
 
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.coroutineScope
 import com.android.systemui.ambient.dagger.AmbientModule
 import com.android.systemui.ambient.touch.TouchHandler
 import dagger.Module
 import dagger.Provides
 import dagger.multibindings.ElementsIntoSet
 import javax.inject.Named
+import kotlinx.coroutines.CoroutineScope
 
 @Module
 interface AmbientTouchModule {
@@ -33,6 +35,12 @@
             return lifecycleOwner.lifecycle
         }
 
+        @JvmStatic
+        @Provides
+        fun providesLifecycleScope(lifecycle: Lifecycle): CoroutineScope {
+            return lifecycle.coroutineScope
+        }
+
         @Provides
         @ElementsIntoSet
         fun providesDreamTouchHandlers(
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 40a141d..e2089bb 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -24,7 +24,6 @@
 import androidx.compose.ui.input.key.type
 import androidx.core.graphics.drawable.toBitmap
 import com.android.compose.animation.scene.Back
-import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserAction
@@ -43,7 +42,6 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor
-import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.user.ui.viewmodel.UserActionViewModel
 import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
@@ -67,7 +65,9 @@
 /** Holds UI state and handles user input on bouncer UIs. */
 class BouncerViewModel(
     @Application private val applicationContext: Context,
-    @Application private val applicationScope: CoroutineScope,
+    @Deprecated("TODO(b/354270224): remove this. Injecting CoroutineScope to view-models is banned")
+    @Application
+    private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
     private val bouncerInteractor: BouncerInteractor,
     private val inputMethodInteractor: InputMethodInteractor,
@@ -91,14 +91,13 @@
                 initialValue = null,
             )
 
-    val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
-        bouncerInteractor.dismissDestination
-            .map(::destinationSceneMap)
-            .stateIn(
-                applicationScope,
-                SharingStarted.WhileSubscribed(),
-                initialValue = destinationSceneMap(Scenes.Lockscreen),
+    val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+        bouncerInteractor.dismissDestination.map { prevScene ->
+            mapOf(
+                Back to UserActionResult(prevScene),
+                Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
             )
+        }
 
     val message: BouncerMessageViewModel = bouncerMessageViewModel
 
@@ -328,8 +327,7 @@
                 { message },
                 failedAttempts,
                 remainingAttempts,
-            )
-                ?: message
+            ) ?: message
         } else {
             message
         }
@@ -346,8 +344,7 @@
                     .KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE,
                 { message },
                 failedAttempts,
-            )
-                ?: message
+            ) ?: message
         } else {
             message
         }
@@ -375,12 +372,6 @@
         }
     }
 
-    private fun destinationSceneMap(prevScene: SceneKey) =
-        mapOf(
-            Back to UserActionResult(prevScene),
-            Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
-        )
-
     /**
      * Notifies that a key event has occurred.
      *
@@ -390,8 +381,7 @@
         return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent(
             keyEvent.type,
             keyEvent.nativeKeyEvent.keyCode
-        )
-            ?: false
+        ) ?: false
     }
 
     data class DialogViewModel(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt
index 81feb44..2352841f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt
@@ -19,14 +19,16 @@
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.CoreStartable
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.util.kotlin.pairwise
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.drop
 import kotlinx.coroutines.flow.filterNotNull
@@ -40,12 +42,13 @@
 @Inject
 constructor(
     @Background private val backgroundScope: CoroutineScope,
-    private val communalInteractor: CommunalInteractor,
+    private val communalSceneInteractor: CommunalSceneInteractor,
+    private val keyguardInteractor: KeyguardInteractor,
     private val uiEventLogger: UiEventLogger,
 ) : CoreStartable {
 
     override fun start() {
-        communalInteractor.transitionState
+        communalSceneInteractor.transitionState
             .map { state ->
                 when {
                     state.isOnCommunal() -> CommunalUiEvent.COMMUNAL_HUB_SHOWN
@@ -60,22 +63,46 @@
             .onEach { uiEvent -> uiEventLogger.log(uiEvent) }
             .launchIn(backgroundScope)
 
-        communalInteractor.transitionState
+        communalSceneInteractor.transitionState
             .pairwise()
-            .map { (old, new) ->
+            .combine(keyguardInteractor.isDreamingWithOverlay) { (old, new), isDreaming ->
                 when {
                     new.isOnCommunal() && old.isSwipingToCommunal() ->
-                        CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_FINISH
+                        if (isDreaming) {
+                            CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_FINISH
+                        } else {
+                            CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_FINISH
+                        }
                     new.isOnCommunal() && old.isSwipingFromCommunal() ->
-                        CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_CANCEL
+                        if (isDreaming) {
+                            CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_CANCEL
+                        } else {
+                            CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_CANCEL
+                        }
                     new.isNotOnCommunal() && old.isSwipingFromCommunal() ->
-                        CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_FINISH
+                        if (isDreaming) {
+                            CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_FINISH
+                        } else {
+                            CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_FINISH
+                        }
                     new.isNotOnCommunal() && old.isSwipingToCommunal() ->
-                        CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_CANCEL
+                        if (isDreaming) {
+                            CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_CANCEL
+                        } else {
+                            CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_CANCEL
+                        }
                     new.isSwipingToCommunal() && old.isNotOnCommunal() ->
-                        CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_START
+                        if (isDreaming) {
+                            CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_SWIPE_START
+                        } else {
+                            CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_ENTER_START
+                        }
                     new.isSwipingFromCommunal() && old.isOnCommunal() ->
-                        CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_START
+                        if (isDreaming) {
+                            CommunalUiEvent.COMMUNAL_HUB_TO_DREAM_SWIPE_START
+                        } else {
+                            CommunalUiEvent.COMMUNAL_HUB_SWIPE_TO_EXIT_START
+                        }
                     else -> null
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
index 6e345fe..9ce8cf7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
@@ -56,6 +56,19 @@
         )
     }
 
+    /** Logs a tap widget event for metrics. No-op if widget is not loggable. */
+    fun logTapWidget(componentName: String, rank: Int) {
+        if (!componentName.isLoggable()) {
+            return
+        }
+
+        statsLogProxy.writeCommunalHubWidgetEventReported(
+            SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__TAP,
+            componentName,
+            rank,
+        )
+    }
+
     /** Logs loggable widgets and the total widget count as a [StatsEvent]. */
     fun logWidgetsSnapshot(
         statsEvents: MutableList<StatsEvent>,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
index 4ab56cc..4711d88 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
@@ -54,14 +54,20 @@
     COMMUNAL_HUB_SWIPE_UP_TO_BOUNCER(1573),
     @UiEvent(doc = "User performs a swipe down gesture from top to enter shade")
     COMMUNAL_HUB_SWIPE_DOWN_TO_SHADE(1574),
-    @UiEvent(doc = "User performs a tap gesture on the UMO in Communal Hub")
-    COMMUNAL_HUB_UMO_TAP(1858),
-    @UiEvent(
-        doc =
-            "A transition from dream to Communal Hub starts. This can be triggered by a tap on " +
-                "the dream."
-    )
-    FROM_DREAM_TO_COMMUNAL_HUB_TRANSITION_START(1859);
+    @UiEvent(doc = "User starts the swipe gesture to enter the Communal Hub from Dream")
+    DREAM_TO_COMMUNAL_HUB_SWIPE_START(1860),
+    @UiEvent(doc = "User finishes the swipe gesture to enter the Communal Hub from Dream")
+    DREAM_TO_COMMUNAL_HUB_SWIPE_FINISH(1861),
+    @UiEvent(doc = "User cancels the swipe gesture to enter the Communal Hub from Dream")
+    DREAM_TO_COMMUNAL_HUB_SWIPE_CANCEL(1862),
+    @UiEvent(doc = "User starts the swipe gesture to exit the Communal Hub to go to Dream")
+    COMMUNAL_HUB_TO_DREAM_SWIPE_START(1863),
+    @UiEvent(doc = "User finishes the swipe gesture to exit the Communal Hub to go to Dream")
+    COMMUNAL_HUB_TO_DREAM_SWIPE_FINISH(1864),
+    @UiEvent(doc = "User cancels the swipe gesture to exit the Communal Hub to go to Dream")
+    COMMUNAL_HUB_TO_DREAM_SWIPE_CANCEL(1865),
+    @UiEvent(doc = "A transition from Dream to Communal Hub starts due to dream awakening")
+    DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START(1866);
 
     override fun getId(): Int {
         return id
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 623e702..4be93cc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -29,9 +29,12 @@
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.flowOf
 
 /** The base view model for the communal hub. */
@@ -57,6 +60,26 @@
     val selectedKey: StateFlow<String?>
         get() = _selectedKey
 
+    private val _isTouchConsumed: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    /** Whether an element inside the lazy grid is actively consuming touches */
+    val isTouchConsumed: Flow<Boolean> = _isTouchConsumed.asStateFlow()
+
+    private val _isNestedScrolling: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+    /** Whether the lazy grid is reporting scrolling within itself */
+    val isNestedScrolling: Flow<Boolean> = _isNestedScrolling.asStateFlow()
+
+    /**
+     * Whether touch is available to be consumed by a touch handler. Touch is available during
+     * nested scrolling as lazy grid reports this for all scroll directions that it detects. In the
+     * case that there is consumed scrolling on a nested element, such as an AndroidView, no nested
+     * scrolling will be reported. It is up to the flow consumer to determine whether the nested
+     * scroll can be applied. In the communal case, this would be identifying the scroll as
+     * vertical, which the lazy horizontal grid does not handle.
+     */
+    val glanceableTouchAvailable: Flow<Boolean> = anyOf(not(isTouchConsumed), isNestedScrolling)
+
     /** Accessibility delegate to be set on CommunalAppWidgetHostView. */
     open val widgetAccessibilityDelegate: View.AccessibilityDelegate? = null
 
@@ -139,6 +162,12 @@
         priority: Int,
     ) {}
 
+    /** Called as the UI detects a tap event on the widget. */
+    open fun onTapWidget(
+        componentName: ComponentName,
+        priority: Int,
+    ) {}
+
     /**
      * Called as the UI requests reordering widgets.
      *
@@ -194,4 +223,28 @@
     fun setSelectedKey(key: String?) {
         _selectedKey.value = key
     }
+
+    /** Invoked once touches inside the lazy grid are consumed */
+    fun onHubTouchConsumed() {
+        if (_isTouchConsumed.value) {
+            return
+        }
+
+        _isTouchConsumed.value = true
+    }
+
+    /** Invoked when nested scrolling begins on the lazy grid */
+    fun onNestedScrolling() {
+        if (_isNestedScrolling.value) {
+            return
+        }
+
+        _isNestedScrolling.value = true
+    }
+
+    /** Resets nested scroll and touch consumption state */
+    fun onResetTouchState() {
+        _isTouchConsumed.value = false
+        _isNestedScrolling.value = false
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index e1408a0..bbd8596 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -19,6 +19,7 @@
 import android.graphics.Color
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.util.CommunalColors
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -58,9 +59,17 @@
     dreamToGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
     glanceableHubToDreamTransitionViewModel: GlanceableHubToDreamingTransitionViewModel,
     communalInteractor: CommunalInteractor,
-    communalSceneInteractor: CommunalSceneInteractor,
+    private val communalSceneInteractor: CommunalSceneInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor
 ) {
+    /**
+     * Snaps to [CommunalScenes.Communal], showing the glanceable hub immediately without any
+     * transition.
+     */
+    fun snapToCommunal() {
+        communalSceneInteractor.snapToScene(CommunalScenes.Communal)
+    }
+
     // Show UMO on glanceable hub immediately on transition into glanceable hub
     private val showUmoFromOccludedToGlanceableHub: Flow<Boolean> =
         keyguardTransitionInteractor
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 1e087f7..3fc8b09 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal.ui.viewmodel
 
+import android.content.ComponentName
 import android.content.res.Resources
 import android.os.Bundle
 import android.view.View
@@ -25,6 +26,7 @@
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.log.CommunalMetricsLogger
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -92,6 +94,7 @@
     private val shadeInteractor: ShadeInteractor,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
     @CommunalLog logBuffer: LogBuffer,
+    private val metricsLogger: CommunalMetricsLogger,
 ) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) {
 
     private val _isMediaHostVisible =
@@ -260,6 +263,10 @@
         }
     }
 
+    override fun onTapWidget(componentName: ComponentName, priority: Int) {
+        metricsLogger.logTapWidget(componentName.flattenToString(), priority)
+    }
+
     fun onClick() {
         keyguardIndicationController.showActionToUnlock()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 3985769..03ef17b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -128,7 +128,7 @@
                 Box(
                     modifier =
                         Modifier.fillMaxSize()
-                            .background(LocalAndroidColorScheme.current.onSecondaryFixed),
+                            .background(LocalAndroidColorScheme.current.surfaceDim),
                 ) {
                     CommunalHub(
                         viewModel = communalViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 83fa001..9823985 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -56,6 +56,7 @@
 import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent;
 import com.android.systemui.ambient.touch.scrim.ScrimManager;
 import com.android.systemui.communal.domain.interactor.CommunalInteractor;
+import com.android.systemui.communal.shared.log.CommunalUiEvent;
 import com.android.systemui.communal.shared.model.CommunalScenes;
 import com.android.systemui.complication.Complication;
 import com.android.systemui.complication.dagger.ComplicationComponent;
@@ -407,6 +408,7 @@
 
     @Override
     public void onWakeRequested() {
+        mUiEventLogger.log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START);
         mCommunalInteractor.changeScene(CommunalScenes.Communal, null);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index d0beb7a..8990505 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -292,15 +292,6 @@
     val WM_ENABLE_SHELL_TRANSITIONS =
         sysPropBooleanFlag("persist.wm.debug.shell_transit", default = true)
 
-    // TODO(b/254513207): Tracking Bug
-    @Keep
-    @JvmField
-    val WM_ENABLE_PARTIAL_SCREEN_SHARING =
-        releasedFlag(
-            name = "enable_record_task_content",
-            namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
-        )
-
     // TODO(b/256873975): Tracking Bug
     @JvmField
     @Keep
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
index bdc18b3..0e19d87 100644
--- a/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
@@ -22,6 +22,8 @@
  * @see android.view.inputmethod.InputMethodInfo
  */
 data class InputMethodModel(
+    /** A unique ID for the user associated with this input method. */
+    val userId: Int,
     /** A unique ID for this input method. */
     val imeId: String,
     /** The subtypes of this IME (may be empty). */
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
index 5f316c4..c6fdc32 100644
--- a/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
@@ -18,7 +18,6 @@
 
 import android.annotation.SuppressLint
 import android.os.UserHandle
-import android.view.inputmethod.InputMethodInfo
 import android.view.inputmethod.InputMethodManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -34,18 +33,27 @@
 
 /** Provides access to input-method related application state in the bouncer. */
 interface InputMethodRepository {
+
     /**
      * Creates and returns a new `Flow` of installed input methods that are enabled for the
      * specified user.
      *
+     * @param user The user to query.
      * @param fetchSubtypes Whether to fetch the IME Subtypes as well (requires an additional IPC
      *   call for each IME, avoid if not needed).
      * @see InputMethodManager.getEnabledInputMethodListAsUser
      */
-    suspend fun enabledInputMethods(userId: Int, fetchSubtypes: Boolean): Flow<InputMethodModel>
+    suspend fun enabledInputMethods(
+        user: UserHandle,
+        fetchSubtypes: Boolean,
+    ): Flow<InputMethodModel>
 
-    /** Returns enabled subtypes for the currently selected input method. */
-    suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype>
+    /**
+     * Returns enabled subtypes for the currently selected input method.
+     *
+     * @param user The user to query.
+     */
+    suspend fun selectedInputMethodSubtypes(user: UserHandle): List<InputMethodModel.Subtype>
 
     /**
      * Shows the system's input method picker dialog.
@@ -67,20 +75,22 @@
 ) : InputMethodRepository {
 
     override suspend fun enabledInputMethods(
-        userId: Int,
+        user: UserHandle,
         fetchSubtypes: Boolean
     ): Flow<InputMethodModel> {
         return withContext(backgroundDispatcher) {
-                inputMethodManager.getEnabledInputMethodListAsUser(UserHandle.of(userId))
+                inputMethodManager.getEnabledInputMethodListAsUser(user)
             }
             .asFlow()
             .map { inputMethodInfo ->
                 InputMethodModel(
+                    userId = user.identifier,
                     imeId = inputMethodInfo.id,
                     subtypes =
                         if (fetchSubtypes) {
                             enabledInputMethodSubtypes(
-                                inputMethodInfo,
+                                user = user,
+                                imeId = inputMethodInfo.id,
                                 allowsImplicitlyEnabledSubtypes = true
                             )
                         } else {
@@ -90,11 +100,19 @@
             }
     }
 
-    override suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype> {
-        return enabledInputMethodSubtypes(
-            inputMethodInfo = null, // Fetch subtypes for the currently-selected IME.
-            allowsImplicitlyEnabledSubtypes = false
-        )
+    override suspend fun selectedInputMethodSubtypes(
+        user: UserHandle,
+    ): List<InputMethodModel.Subtype> {
+        val selectedIme = inputMethodManager.getCurrentInputMethodInfoAsUser(user)
+        return if (selectedIme == null) {
+            emptyList()
+        } else {
+            enabledInputMethodSubtypes(
+                user = user,
+                imeId = selectedIme.id,
+                allowsImplicitlyEnabledSubtypes = false
+            )
+        }
     }
 
     @SuppressLint("MissingPermission")
@@ -107,21 +125,23 @@
     /**
      * Returns a list of enabled input method subtypes for the specified input method info.
      *
-     * @param inputMethodInfo The [InputMethodInfo] whose subtypes list will be returned. If `null`,
-     *   returns enabled subtypes for the currently selected [InputMethodInfo].
+     * @param user The user to query.
+     * @param imeId The ID of the input method whose subtypes list will be returned.
      * @param allowsImplicitlyEnabledSubtypes Whether to allow to return the implicitly enabled
      *   subtypes. If an input method info doesn't have enabled subtypes, the framework will
      *   implicitly enable subtypes according to the current system language.
-     * @see InputMethodManager.getEnabledInputMethodSubtypeList
+     * @see InputMethodManager.getEnabledInputMethodSubtypeListAsUser
      */
     private suspend fun enabledInputMethodSubtypes(
-        inputMethodInfo: InputMethodInfo?,
+        user: UserHandle,
+        imeId: String,
         allowsImplicitlyEnabledSubtypes: Boolean
     ): List<InputMethodModel.Subtype> {
         return withContext(backgroundDispatcher) {
-                inputMethodManager.getEnabledInputMethodSubtypeList(
-                    inputMethodInfo,
-                    allowsImplicitlyEnabledSubtypes
+                inputMethodManager.getEnabledInputMethodSubtypeListAsUser(
+                    imeId,
+                    allowsImplicitlyEnabledSubtypes,
+                    user
                 )
             }
             .map {
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
index c54aa7f..d3ef178 100644
--- a/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.inputmethod.domain.interactor
 
+import android.os.UserHandle
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.inputmethod.data.repository.InputMethodRepository
 import javax.inject.Inject
@@ -36,14 +37,16 @@
      * Method adapted from `com.android.inputmethod.latin.Utils`.
      */
     suspend fun hasMultipleEnabledImesOrSubtypes(userId: Int): Boolean {
+        val user = UserHandle.of(userId)
         // Count IMEs that either have no subtypes, or have at least one non-auxiliary subtype.
         val matchingInputMethods =
             repository
-                .enabledInputMethods(userId, fetchSubtypes = true)
+                .enabledInputMethods(user, fetchSubtypes = true)
                 .filter { ime -> ime.subtypes.isEmpty() || ime.subtypes.any { !it.isAuxiliary } }
                 .take(2) // Short-circuit if we find at least 2 matching IMEs.
 
-        return matchingInputMethods.count() > 1 || repository.selectedInputMethodSubtypes().size > 1
+        return matchingInputMethods.count() > 1 ||
+            repository.selectedInputMethodSubtypes(user).size > 1
     }
 
     /** Shows the system's input method picker dialog. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index d1a8463..2f41c0b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -62,21 +62,21 @@
 const val TAG = "KeyguardUnlock"
 
 /**
- * Starting scale factor for the app/launcher surface behind the keyguard, when it's animating
- * in during keyguard exit.
+ * Starting scale factor for the app/launcher surface behind the keyguard, when it's animating in
+ * during keyguard exit.
  */
 const val SURFACE_BEHIND_START_SCALE_FACTOR = 0.95f
 
 /**
- * How much to translate the surface behind the keyguard at the beginning of the exit animation,
- * in terms of percentage of the surface's height.
+ * How much to translate the surface behind the keyguard at the beginning of the exit animation, in
+ * terms of percentage of the surface's height.
  */
 const val SURFACE_BEHIND_START_TRANSLATION_Y = 0.05f
 
 /**
- * Y coordinate of the pivot point for the scale effect on the surface behind the keyguard. This
- * is expressed as percentage of the surface's height, so 0.66f means the surface will scale up
- * from the point at (width / 2, height * 0.66).
+ * Y coordinate of the pivot point for the scale effect on the surface behind the keyguard. This is
+ * expressed as percentage of the surface's height, so 0.66f means the surface will scale up from
+ * the point at (width / 2, height * 0.66).
  */
 const val SURFACE_BEHIND_SCALE_PIVOT_Y = 0.66f
 
@@ -155,19 +155,20 @@
  * [notifyStartSurfaceBehindRemoteAnimation] by [KeyguardViewMediator].
  */
 @SysUISingleton
-class KeyguardUnlockAnimationController @Inject constructor(
-        private val windowManager: WindowManager,
-        @Main private val resources: Resources,
-        private val keyguardStateController: KeyguardStateController,
-        private val
-    keyguardViewMediator: Lazy<KeyguardViewMediator>,
-        private val keyguardViewController: KeyguardViewController,
-        private val featureFlags: FeatureFlags,
-        private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
-        private val statusBarStateController: SysuiStatusBarStateController,
-        private val notificationShadeWindowController: NotificationShadeWindowController,
-        private val powerManager: PowerManager,
-        private val wallpaperManager: WallpaperManager,
+open class KeyguardUnlockAnimationController
+@Inject
+constructor(
+    private val windowManager: WindowManager,
+    @Main private val resources: Resources,
+    private val keyguardStateController: KeyguardStateController,
+    private val keyguardViewMediator: Lazy<KeyguardViewMediator>,
+    private val keyguardViewController: KeyguardViewController,
+    private val featureFlags: FeatureFlags,
+    private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
+    private val statusBarStateController: SysuiStatusBarStateController,
+    private val notificationShadeWindowController: NotificationShadeWindowController,
+    private val powerManager: PowerManager,
+    private val wallpaperManager: WallpaperManager,
 ) : KeyguardStateController.Callback, ISysuiUnlockAnimationController.Stub() {
 
     interface KeyguardUnlockAnimationListener {
@@ -221,8 +222,8 @@
     var playingCannedUnlockAnimation = false
 
     /**
-     * Whether we reached the swipe gesture threshold to dismiss keyguard, or restore it, once
-     * and should ignore any future changes to the dismiss amount before the animation finishes.
+     * Whether we reached the swipe gesture threshold to dismiss keyguard, or restore it, once and
+     * should ignore any future changes to the dismiss amount before the animation finishes.
      */
     var dismissAmountThresholdsReached = false
 
@@ -235,9 +236,7 @@
      */
     private var launcherUnlockController: ILauncherUnlockAnimationController? = null
 
-    /**
-     * Fully qualified class name of the launcher activity
-     */
+    /** Fully qualified class name of the launcher activity */
     private var launcherActivityClass: String? = null
 
     private val listeners = ArrayList<KeyguardUnlockAnimationListener>()
@@ -248,8 +247,8 @@
      * transition, but that's okay!
      */
     override fun setLauncherUnlockController(
-            activityClass: String,
-            callback: ILauncherUnlockAnimationController?
+        activityClass: String,
+        callback: ILauncherUnlockAnimationController?
     ) {
         launcherActivityClass = activityClass
         launcherUnlockController = callback
@@ -274,8 +273,7 @@
      * If we're unlocking via biometrics, PIN entry, or from clicking a notification, a canned
      * animation is started in [playCannedUnlockAnimation].
      */
-    @VisibleForTesting
-    var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null
+    @VisibleForTesting var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null
     private var surfaceBehindRemoteAnimationTargets: Array<RemoteAnimationTarget>? = null
     private var openingWallpaperTargets: Array<RemoteAnimationTarget>? = null
     private var closingWallpaperTargets: Array<RemoteAnimationTarget>? = null
@@ -291,8 +289,7 @@
      */
     private var surfaceBehindAlpha = 1f
 
-    @VisibleForTesting
-    var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f)
+    @VisibleForTesting var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f)
 
     var wallpaperCannedUnlockAnimator = ValueAnimator.ofFloat(0f, 1f)
 
@@ -310,8 +307,7 @@
      * Animator that animates in the surface behind the keyguard. This is used to play a canned
      * animation on the surface, if we're not doing a swipe gesture.
      */
-    @VisibleForTesting
-    val surfaceBehindEntryAnimator = ValueAnimator.ofFloat(0f, 1f)
+    @VisibleForTesting val surfaceBehindEntryAnimator = ValueAnimator.ofFloat(0f, 1f)
 
     /** Rounded corner radius to apply to the surface behind the keyguard. */
     private var roundedCornerRadius = 0f
@@ -322,8 +318,7 @@
      * window like any other app. This can be true while [willUnlockWithSmartspaceTransition] is
      * false, if the smartspace is not available or was not ready in time.
      */
-    @VisibleForTesting
-    var willUnlockWithInWindowLauncherAnimations: Boolean = false
+    @VisibleForTesting var willUnlockWithInWindowLauncherAnimations: Boolean = false
 
     /**
      * Whether we called [ILauncherUnlockAnimationController.prepareForUnlock], but have not yet
@@ -353,49 +348,64 @@
                 surfaceBehindAlpha = valueAnimator.animatedValue as Float
                 updateSurfaceBehindAppearAmount()
             }
-            addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator) {
-                    // If we animated the surface alpha to 0f, it means we cancelled a swipe to
-                    // dismiss. In this case, we should ask the KeyguardViewMediator to end the
-                    // remote animation to hide the surface behind the keyguard, but should *not*
-                    // call onKeyguardExitRemoteAnimationFinished since that will hide the keyguard
-                    // and unlock the device as well as hiding the surface.
-                    if (surfaceBehindAlpha == 0f) {
-                        Log.d(TAG, "surfaceBehindAlphaAnimator#onAnimationEnd")
-                        surfaceBehindRemoteAnimationTargets = null
-                        openingWallpaperTargets = null
-                        closingWallpaperTargets = null
-                        keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation(
-                            false /* cancelled */)
-                    } else {
-                        Log.d(TAG, "skip finishSurfaceBehindRemoteAnimation" +
-                                " surfaceBehindAlpha=$surfaceBehindAlpha")
+            addListener(
+                object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator) {
+                        // If we animated the surface alpha to 0f, it means we cancelled a swipe to
+                        // dismiss. In this case, we should ask the KeyguardViewMediator to end the
+                        // remote animation to hide the surface behind the keyguard, but should
+                        // *not* call onKeyguardExitRemoteAnimationFinished since that will hide the
+                        // keyguard and unlock the device as well as hiding the surface.
+                        if (surfaceBehindAlpha == 0f) {
+                            Log.d(TAG, "surfaceBehindAlphaAnimator#onAnimationEnd")
+                            surfaceBehindRemoteAnimationTargets = null
+                            openingWallpaperTargets = null
+                            closingWallpaperTargets = null
+                            keyguardViewMediator
+                                .get()
+                                .finishSurfaceBehindRemoteAnimation(false /* cancelled */)
+                        } else {
+                            Log.d(
+                                TAG,
+                                "skip finishSurfaceBehindRemoteAnimation" +
+                                    " surfaceBehindAlpha=$surfaceBehindAlpha"
+                            )
+                        }
                     }
                 }
-            })
+            )
         }
 
         with(wallpaperCannedUnlockAnimator) {
-            duration = if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
-                    else LAUNCHER_ICONS_ANIMATION_DURATION_MS
-            interpolator = if (fasterUnlockTransition()) Interpolators.LINEAR
-                    else Interpolators.ALPHA_OUT
+            duration =
+                if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
+                else LAUNCHER_ICONS_ANIMATION_DURATION_MS
+            interpolator =
+                if (fasterUnlockTransition()) Interpolators.LINEAR else Interpolators.ALPHA_OUT
             addUpdateListener { valueAnimator: ValueAnimator ->
                 setWallpaperAppearAmount(
-                        valueAnimator.animatedValue as Float, openingWallpaperTargets)
+                    valueAnimator.animatedValue as Float,
+                    openingWallpaperTargets
+                )
             }
-            addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationStart(animation: Animator) {
-                    super.onAnimationStart(animation)
-                    Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
+            addListener(
+                object : AnimatorListenerAdapter() {
+                    override fun onAnimationStart(animation: Animator) {
+                        super.onAnimationStart(animation)
+                        Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
+                    }
+
+                    override fun onAnimationEnd(animation: Animator) {
+                        Log.d(TAG, "wallpaperCannedUnlockAnimator#onAnimationEnd")
+                        keyguardViewMediator
+                            .get()
+                            .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
+                                false /* cancelled */
+                            )
+                        Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
+                    }
                 }
-                override fun onAnimationEnd(animation: Animator) {
-                    Log.d(TAG, "wallpaperCannedUnlockAnimator#onAnimationEnd")
-                    keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
-                        false /* cancelled */)
-                    Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
-                }
-            })
+            )
         }
 
         if (fasterUnlockTransition()) {
@@ -405,7 +415,9 @@
                 interpolator = Interpolators.LINEAR
                 addUpdateListener { valueAnimator: ValueAnimator ->
                     setWallpaperAppearAmount(
-                            valueAnimator.animatedValue as Float, closingWallpaperTargets)
+                        valueAnimator.animatedValue as Float,
+                        closingWallpaperTargets
+                    )
                 }
             }
         }
@@ -418,15 +430,19 @@
                 surfaceBehindAlpha = valueAnimator.animatedValue as Float
                 setSurfaceBehindAppearAmount(valueAnimator.animatedValue as Float)
             }
-            addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator) {
-                    Log.d(TAG, "surfaceBehindEntryAnimator#onAnimationEnd")
-                    playingCannedUnlockAnimation = false
-                    keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
-                        false /* cancelled */
-                    )
+            addListener(
+                object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator) {
+                        Log.d(TAG, "surfaceBehindEntryAnimator#onAnimationEnd")
+                        playingCannedUnlockAnimation = false
+                        keyguardViewMediator
+                            .get()
+                            .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
+                                false /* cancelled */
+                            )
+                    }
                 }
-            })
+            )
         }
 
         // Listen for changes in the dismiss amount.
@@ -436,9 +452,7 @@
             resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat()
     }
 
-    /**
-     * Add a listener to be notified of various stages of the unlock animation.
-     */
+    /** Add a listener to be notified of various stages of the unlock animation. */
     fun addKeyguardUnlockAnimationListener(listener: KeyguardUnlockAnimationListener) {
         listeners.add(listener)
     }
@@ -454,11 +468,11 @@
     fun canPerformInWindowLauncherAnimations(): Boolean {
         // TODO(b/278086361): Refactor in-window animations.
         return !KeyguardWmStateRefactor.isEnabled &&
-                isSupportedLauncherUnderneath() &&
-                // If the launcher is underneath, but we're about to launch an activity, don't do
-                // the animations since they won't be visible.
-                !notificationShadeWindowController.isLaunchingActivity &&
-                launcherUnlockController != null
+            isSupportedLauncherUnderneath() &&
+            // If the launcher is underneath, but we're about to launch an activity, don't do
+            // the animations since they won't be visible.
+            !notificationShadeWindowController.isLaunchingActivity &&
+            launcherUnlockController != null
     }
 
     /**
@@ -469,8 +483,11 @@
     private fun logInWindowAnimationConditions() {
         Log.wtf(TAG, "canPerformInWindowLauncherAnimations expected all of these to be true: ")
         Log.wtf(TAG, "  isNexusLauncherUnderneath: ${isSupportedLauncherUnderneath()}")
-        Log.wtf(TAG, "  !notificationShadeWindowController.isLaunchingActivity: " +
-                "${!notificationShadeWindowController.isLaunchingActivity}")
+        Log.wtf(
+            TAG,
+            "  !notificationShadeWindowController.isLaunchingActivity: " +
+                "${!notificationShadeWindowController.isLaunchingActivity}"
+        )
         Log.wtf(TAG, "  launcherUnlockController != null: ${launcherUnlockController != null}")
         Log.wtf(TAG, "  !isFoldable(context): ${!isFoldable(resources)}")
     }
@@ -480,8 +497,10 @@
      * changed.
      */
     override fun onKeyguardGoingAwayChanged() {
-        if (keyguardStateController.isKeyguardGoingAway &&
-                !statusBarStateController.leaveOpenOnKeyguardHide()) {
+        if (
+            keyguardStateController.isKeyguardGoingAway &&
+                !statusBarStateController.leaveOpenOnKeyguardHide()
+        ) {
             prepareForInWindowLauncherAnimations()
         }
 
@@ -489,16 +508,22 @@
         // make sure that we've left the launcher at 100% unlocked. This is a fail-safe to prevent
         // against "tiny launcher" and similar states where the launcher is left in the prepared to
         // animate state.
-        if (!keyguardStateController.isKeyguardGoingAway &&
-                willUnlockWithInWindowLauncherAnimations) {
+        if (
+            !keyguardStateController.isKeyguardGoingAway && willUnlockWithInWindowLauncherAnimations
+        ) {
             try {
-                launcherUnlockController?.setUnlockAmount(1f,
-                        biometricUnlockControllerLazy.get().isWakeAndUnlock /* forceIfAnimating */)
+                launcherUnlockController?.setUnlockAmount(
+                    1f,
+                    biometricUnlockControllerLazy.get().isWakeAndUnlock /* forceIfAnimating */
+                )
             } catch (e: DeadObjectException) {
-                Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null in " +
+                Log.e(
+                    TAG,
+                    "launcherUnlockAnimationController was dead, but non-null in " +
                         "onKeyguardGoingAwayChanged(). Catching exception as this should mean " +
                         "Launcher is in the process of being destroyed, but the IPC to System UI " +
-                        "telling us hasn't arrived yet.")
+                        "telling us hasn't arrived yet."
+                )
             }
         }
     }
@@ -525,22 +550,26 @@
         // Grab the bounds of our lockscreen smartspace and send them to launcher so they can
         // position their smartspace there initially, then animate it to its resting position.
         if (willUnlockWithSmartspaceTransition) {
-            lockscreenSmartspaceBounds = Rect().apply {
-                lockscreenSmartspace!!.getBoundsOnScreen(this)
+            lockscreenSmartspaceBounds =
+                Rect().apply {
+                    lockscreenSmartspace!!.getBoundsOnScreen(this)
 
-                // The smartspace container on the lockscreen has left and top padding to align it
-                // with other lockscreen content. This padding is inside the bounds on screen, so
-                // add it to those bounds so that the padding-less launcher smartspace is properly
-                // aligned.
-                offset(lockscreenSmartspace!!.paddingLeft, lockscreenSmartspace!!.paddingTop)
+                    // The smartspace container on the lockscreen has left and top padding to align
+                    // it with other lockscreen content. This padding is inside the bounds on
+                    // screen, so add it to those bounds so that the padding-less launcher
+                    // smartspace is properly aligned.
+                    offset(lockscreenSmartspace!!.paddingLeft, lockscreenSmartspace!!.paddingTop)
 
-                // Also offset by the current card's top padding, if it has any. This allows us to
-                // align the tops of the lockscreen/launcher smartspace cards. Some cards, such as
-                // the three-line date/weather/alarm card, only have three lines on lockscreen but
-                // two on launcher.
-                offset(0, (lockscreenSmartspace
-                        as? BcSmartspaceDataPlugin.SmartspaceView)?.currentCardTopPadding ?: 0)
-            }
+                    // Also offset by the current card's top padding, if it has any. This allows us
+                    // to align the tops of the lockscreen/launcher smartspace cards. Some cards,
+                    // such as the three-line date/weather/alarm card, only have three lines on
+                    // lockscreen but two on launcher.
+                    offset(
+                        0,
+                        (lockscreenSmartspace as? BcSmartspaceDataPlugin.SmartspaceView)
+                            ?.currentCardTopPadding ?: 0
+                    )
+                }
         }
 
         // Currently selected lockscreen smartspace page, or -1 if it's not available.
@@ -583,8 +612,8 @@
         requestedShowSurfaceBehindKeyguard: Boolean
     ) {
         if (surfaceTransactionApplier == null) {
-            surfaceTransactionApplier = SyncRtSurfaceTransactionApplier(
-                    keyguardViewController.viewRootImpl.view)
+            surfaceTransactionApplier =
+                SyncRtSurfaceTransactionApplier(keyguardViewController.viewRootImpl.view)
         }
 
         surfaceBehindRemoteAnimationTargets = targets
@@ -603,8 +632,10 @@
             // surface behind the keyguard to finish unlocking.
             if (keyguardStateController.isFlingingToDismissKeyguard) {
                 playCannedUnlockAnimation()
-            } else if (keyguardStateController.isDismissingFromSwipe &&
-                    willUnlockWithInWindowLauncherAnimations) {
+            } else if (
+                keyguardStateController.isDismissingFromSwipe &&
+                    willUnlockWithInWindowLauncherAnimations
+            ) {
                 // If we're swiping to unlock to the Launcher, and can play in-window animations,
                 // make the launcher surface fully visible and play the in-window unlock animation
                 // on the launcher icons. System UI will remain locked, using the swipe-to-unlock
@@ -615,19 +646,23 @@
 
                 try {
                     launcherUnlockController?.playUnlockAnimation(
-                            true,
-                            unlockAnimationDurationMs() + cannedUnlockStartDelayMs(),
-                            0 /* startDelay */)
+                        true,
+                        unlockAnimationDurationMs() + cannedUnlockStartDelayMs(),
+                        0 /* startDelay */
+                    )
                 } catch (e: DeadObjectException) {
                     // Hello! If you are here investigating a bug where Launcher is blank (no icons)
                     // then the below assumption about Launcher's destruction was incorrect. This
                     // would mean prepareToUnlock was called (blanking Launcher in preparation for
                     // the beginning of the unlock animation), but then somehow we were unable to
                     // call playUnlockAnimation to animate the icons back in.
-                    Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null. " +
+                    Log.e(
+                        TAG,
+                        "launcherUnlockAnimationController was dead, but non-null. " +
                             "Catching exception as this should mean Launcher is in the process " +
                             "of being destroyed, but the IPC to System UI telling us hasn't " +
-                            "arrived yet.")
+                            "arrived yet."
+                    )
                 }
 
                 launcherPreparedForUnlock = false
@@ -643,15 +678,18 @@
         }
 
         // Notify if waking from AOD only
-        val isWakeAndUnlockNotFromDream = biometricUnlockControllerLazy.get().isWakeAndUnlock &&
-            biometricUnlockControllerLazy.get().mode != MODE_WAKE_AND_UNLOCK_FROM_DREAM
+        val isWakeAndUnlockNotFromDream =
+            biometricUnlockControllerLazy.get().isWakeAndUnlock &&
+                biometricUnlockControllerLazy.get().mode != MODE_WAKE_AND_UNLOCK_FROM_DREAM
 
         listeners.forEach {
             it.onUnlockAnimationStarted(
                 playingCannedUnlockAnimation /* playingCannedAnimation */,
                 isWakeAndUnlockNotFromDream /* isWakeAndUnlockNotFromDream */,
                 cannedUnlockStartDelayMs() /* unlockStartDelay */,
-                LAUNCHER_ICONS_ANIMATION_DURATION_MS /* unlockAnimationDuration */) }
+                LAUNCHER_ICONS_ANIMATION_DURATION_MS /* unlockAnimationDuration */
+            )
+        }
 
         // Finish the keyguard remote animation if the dismiss amount has crossed the threshold.
         // Check it here in case there is no more change to the dismiss amount after the last change
@@ -685,8 +723,9 @@
             biometricUnlockControllerLazy.get().isWakeAndUnlock -> {
                 Log.d(TAG, "playCannedUnlockAnimation, isWakeAndUnlock")
                 setSurfaceBehindAppearAmount(1f)
-                keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
-                    false /* cancelled */)
+                keyguardViewMediator
+                    .get()
+                    .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
             }
 
             // Otherwise, we're doing a normal full-window unlock. Start this animator, which will
@@ -698,8 +737,11 @@
         }
 
         if (launcherPreparedForUnlock && !willUnlockWithInWindowLauncherAnimations) {
-            Log.wtf(TAG, "Launcher is prepared for unlock, so we should have started the " +
-                    "in-window animation, however we apparently did not.")
+            Log.wtf(
+                TAG,
+                "Launcher is prepared for unlock, so we should have started the " +
+                    "in-window animation, however we apparently did not."
+            )
             logInWindowAnimationConditions()
         }
     }
@@ -708,7 +750,6 @@
      * Unlock to the launcher, using in-window animations, and the smartspace shared element
      * transition if possible.
      */
-
     @VisibleForTesting
     fun unlockToLauncherWithInWindowAnimations() {
         surfaceBehindAlpha = 1f
@@ -717,26 +758,32 @@
         try {
             // Begin the animation, waiting for the shade to animate out.
             launcherUnlockController?.playUnlockAnimation(
-                    true /* unlocked */,
-                    LAUNCHER_ICONS_ANIMATION_DURATION_MS /* duration */,
-                    cannedUnlockStartDelayMs() /* startDelay */)
+                true /* unlocked */,
+                LAUNCHER_ICONS_ANIMATION_DURATION_MS /* duration */,
+                cannedUnlockStartDelayMs() /* startDelay */
+            )
         } catch (e: DeadObjectException) {
             // Hello! If you are here investigating a bug where Launcher is blank (no icons)
             // then the below assumption about Launcher's destruction was incorrect. This
             // would mean prepareToUnlock was called (blanking Launcher in preparation for
             // the beginning of the unlock animation), but then somehow we were unable to
             // call playUnlockAnimation to animate the icons back in.
-            Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null. " +
+            Log.e(
+                TAG,
+                "launcherUnlockAnimationController was dead, but non-null. " +
                     "Catching exception as this should mean Launcher is in the process " +
                     "of being destroyed, but the IPC to System UI telling us hasn't " +
-                    "arrived yet.")
+                    "arrived yet."
+            )
         }
 
         launcherPreparedForUnlock = false
 
         // Now that the Launcher surface (with its smartspace positioned identically to ours) is
         // visible, hide our smartspace.
-        if (lockscreenSmartspace?.visibility == View.VISIBLE) {
+        if (
+            shouldPerformSmartspaceTransition() && lockscreenSmartspace?.visibility == View.VISIBLE
+        ) {
             lockscreenSmartspace?.visibility = View.INVISIBLE
         }
 
@@ -747,22 +794,31 @@
             fadeOutWallpaper()
         }
 
-        handler.postDelayed({
-            if (keyguardViewMediator.get().isShowingAndNotOccluded &&
-                !keyguardStateController.isKeyguardGoingAway) {
-                    Log.e(TAG, "Finish keyguard exit animation delayed Runnable ran, but we are " +
-                            "showing and not going away.")
-                return@postDelayed
-            }
+        handler.postDelayed(
+            {
+                if (
+                    keyguardViewMediator.get().isShowingAndNotOccluded &&
+                        !keyguardStateController.isKeyguardGoingAway
+                ) {
+                    Log.e(
+                        TAG,
+                        "Finish keyguard exit animation delayed Runnable ran, but we are " +
+                            "showing and not going away."
+                    )
+                    return@postDelayed
+                }
 
-            if (openingWallpaperTargets?.isNotEmpty() == true) {
-                fadeInWallpaper()
-                hideKeyguardViewAfterRemoteAnimation()
-            } else {
-                keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
-                    false /* cancelled */)
-            }
-        }, cannedUnlockStartDelayMs())
+                if (openingWallpaperTargets?.isNotEmpty() == true) {
+                    fadeInWallpaper()
+                    hideKeyguardViewAfterRemoteAnimation()
+                } else {
+                    keyguardViewMediator
+                        .get()
+                        .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
+                }
+            },
+            cannedUnlockStartDelayMs()
+        )
     }
 
     /**
@@ -784,12 +840,14 @@
         // interaction tight.
         if (keyguardStateController.isFlingingToDismissKeyguard) {
             setSurfaceBehindAppearAmount(keyguardStateController.dismissAmount)
-        } else if (keyguardStateController.isDismissingFromSwipe ||
-                keyguardStateController.isSnappingKeyguardBackAfterSwipe) {
+        } else if (
+            keyguardStateController.isDismissingFromSwipe ||
+                keyguardStateController.isSnappingKeyguardBackAfterSwipe
+        ) {
             val totalSwipeDistanceToDismiss =
-                    (DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD)
+                (DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD)
             val swipedDistanceSoFar: Float =
-                    keyguardStateController.dismissAmount - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD
+                keyguardStateController.dismissAmount - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD
             val progress = swipedDistanceSoFar / totalSwipeDistanceToDismiss
             setSurfaceBehindAppearAmount(progress)
         }
@@ -801,10 +859,13 @@
 
             // If the surface is visible or it's about to be, start updating its appearance to
             // reflect the new dismiss amount.
-            if ((keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
-                    keyguardViewMediator.get()
+            if (
+                (keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
+                    keyguardViewMediator
+                        .get()
                         .isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) &&
-                    !playingCannedUnlockAnimation) {
+                    !playingCannedUnlockAnimation
+            ) {
                 updateSurfaceBehindAppearAmount()
             }
         }
@@ -838,11 +899,15 @@
 
         val dismissAmount = keyguardStateController.dismissAmount
 
-        if (dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
-            !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
+        if (
+            dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
+                !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()
+        ) {
             keyguardViewMediator.get().showSurfaceBehindKeyguard()
-        } else if (dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
-                keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
+        } else if (
+            dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
+                keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()
+        ) {
             // We're no longer past the threshold but we are showing the surface. Animate it
             // out.
             keyguardViewMediator.get().hideSurfaceBehindKeyguard()
@@ -868,22 +933,27 @@
         }
 
         // no-op if animation is not requested yet.
-        if (!keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
-                !keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) {
+        if (
+            !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
+                !keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe
+        ) {
             return
         }
 
         val dismissAmount = keyguardStateController.dismissAmount
-        if (dismissAmount >= 1f ||
+        if (
+            dismissAmount >= 1f ||
                 (keyguardStateController.isDismissingFromSwipe &&
-                        // Don't hide if we're flinging during a swipe, since we need to finish
-                        // animating it out. This will be called again after the fling ends.
-                        !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
-                        dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)) {
+                    // Don't hide if we're flinging during a swipe, since we need to finish
+                    // animating it out. This will be called again after the fling ends.
+                    !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
+                    dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)
+        ) {
             setSurfaceBehindAppearAmount(1f)
             dismissAmountThresholdsReached = true
-            keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
-                    false /* cancelled */)
+            keyguardViewMediator
+                .get()
+                .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
         }
     }
 
@@ -894,51 +964,56 @@
      * wallpapers, this transitions between the two wallpapers
      */
     fun setSurfaceBehindAppearAmount(amount: Float, wallpapers: Boolean = true) {
-        val animationAlpha = when {
-            // If we're snapping the keyguard back, immediately begin fading it out.
-            keyguardStateController.isSnappingKeyguardBackAfterSwipe -> amount
-            // If the screen has turned back off, the unlock animation is going to be cancelled,
-            // so set the surface alpha to 0f so it's no longer visible.
-            !powerManager.isInteractive -> 0f
-            else -> surfaceBehindAlpha
-        }
+        val animationAlpha =
+            when {
+                // If we're snapping the keyguard back, immediately begin fading it out.
+                keyguardStateController.isSnappingKeyguardBackAfterSwipe -> amount
+                // If the screen has turned back off, the unlock animation is going to be cancelled,
+                // so set the surface alpha to 0f so it's no longer visible.
+                !powerManager.isInteractive -> 0f
+                else -> surfaceBehindAlpha
+            }
 
         surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
             if (!KeyguardWmStateRefactor.isEnabled) {
                 val surfaceHeight: Int =
-                        surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
+                    surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
 
-                var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
-                        (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
-                        MathUtils.clamp(amount, 0f, 1f))
+                var scaleFactor =
+                    (SURFACE_BEHIND_START_SCALE_FACTOR +
+                        (1f - SURFACE_BEHIND_START_SCALE_FACTOR) * MathUtils.clamp(amount, 0f, 1f))
 
                 // If we're dismissing via swipe to the Launcher, we'll play in-window scale
                 // animations, so don't also scale the window.
-                if (keyguardStateController.isDismissingFromSwipe &&
-                        willUnlockWithInWindowLauncherAnimations) {
+                if (
+                    keyguardStateController.isDismissingFromSwipe &&
+                        willUnlockWithInWindowLauncherAnimations
+                ) {
                     scaleFactor = 1f
                 }
 
                 // Translate up from the bottom.
                 surfaceBehindMatrix.setTranslate(
-                        surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
-                        surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
-                                surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
+                    surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
+                    surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
+                        surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
                 )
 
                 // Scale up from a point at the center-bottom of the surface.
                 surfaceBehindMatrix.postScale(
-                        scaleFactor,
-                        scaleFactor,
-                        keyguardViewController.viewRootImpl.width / 2f,
-                        surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
+                    scaleFactor,
+                    scaleFactor,
+                    keyguardViewController.viewRootImpl.width / 2f,
+                    surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
                 )
 
                 // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
                 // unable to draw
                 val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
-                if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
-                        sc?.isValid == true) {
+                if (
+                    keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+                        sc?.isValid == true
+                ) {
                     with(SurfaceControl.Transaction()) {
                         setMatrix(sc, surfaceBehindMatrix, tmpFloat)
                         setCornerRadius(sc, roundedCornerRadius)
@@ -947,12 +1022,13 @@
                     }
                 } else {
                     applyParamsToSurface(
-                            SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
-                                    surfaceBehindRemoteAnimationTarget.leash)
-                                    .withMatrix(surfaceBehindMatrix)
-                                    .withCornerRadius(roundedCornerRadius)
-                                    .withAlpha(animationAlpha)
-                                    .build()
+                        SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+                                surfaceBehindRemoteAnimationTarget.leash
+                            )
+                            .withMatrix(surfaceBehindMatrix)
+                            .withCornerRadius(roundedCornerRadius)
+                            .withAlpha(animationAlpha)
+                            .build()
                     )
                 }
             }
@@ -969,8 +1045,8 @@
 
                 val fadeOutStart = LOCK_WALLPAPER_FADE_OUT_START_DELAY / total
                 val fadeOutEnd = fadeOutStart + LOCK_WALLPAPER_FADE_OUT_DURATION / total
-                val fadeOutAmount = ((amount - fadeOutStart) / (fadeOutEnd - fadeOutStart))
-                        .coerceIn(0f, 1f)
+                val fadeOutAmount =
+                    ((amount - fadeOutStart) / (fadeOutEnd - fadeOutStart)).coerceIn(0f, 1f)
 
                 setWallpaperAppearAmount(fadeInAmount, openingWallpaperTargets)
                 setWallpaperAppearAmount(1 - fadeOutAmount, closingWallpaperTargets)
@@ -984,18 +1060,19 @@
             // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
             // unable to draw
             val sc: SurfaceControl? = wallpaper.leash
-            if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
-                    sc?.isValid == true) {
+            if (
+                keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+                    sc?.isValid == true
+            ) {
                 with(SurfaceControl.Transaction()) {
                     setAlpha(sc, animationAlpha)
                     apply()
                 }
             } else {
                 applyParamsToSurface(
-                        SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
-                                wallpaper.leash)
-                                .withAlpha(animationAlpha)
-                                .build()
+                    SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(wallpaper.leash)
+                        .withAlpha(animationAlpha)
+                        .build()
                 )
             }
         }
@@ -1019,9 +1096,9 @@
         }
 
         if (!showKeyguard) {
-            // Make sure we made the surface behind fully visible, just in case. It should already be
-            // fully visible. The exit animation is finished, and we should not hold the leash anymore,
-            // so forcing it to 1f.
+            // Make sure we made the surface behind fully visible, just in case. It should already
+            // be fully visible. The exit animation is finished, and we should not hold the leash
+            // anymore, so forcing it to 1f.
             surfaceBehindAlpha = 1f
             setSurfaceBehindAppearAmount(1f)
 
@@ -1061,13 +1138,16 @@
 
             if (!KeyguardWmStateRefactor.isEnabled) {
                 keyguardViewController.hide(
-                        surfaceBehindRemoteAnimationStartTime,
-                        0 /* fadeOutDuration */
+                    surfaceBehindRemoteAnimationStartTime,
+                    0 /* fadeOutDuration */
                 )
             }
         } else {
-            Log.i(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
-                    "showing. Ignoring...")
+            Log.i(
+                TAG,
+                "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
+                    "showing. Ignoring..."
+            )
         }
     }
 
@@ -1099,7 +1179,8 @@
         surfaceBehindAlphaAnimator.reverse()
     }
 
-    private fun shouldPerformSmartspaceTransition(): Boolean {
+    /** Note: declared open for ease of testing */
+    open fun shouldPerformSmartspaceTransition(): Boolean {
         // Feature is disabled, so we don't want to.
         if (!featureFlags.isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED)) {
             return false
@@ -1107,9 +1188,11 @@
 
         // If our controllers are null, or we haven't received a smartspace state from Launcher yet,
         // we will not be doing any smartspace transitions today.
-        if (launcherUnlockController == null ||
-            lockscreenSmartspace == null ||
-            launcherSmartspaceState == null) {
+        if (
+            launcherUnlockController == null ||
+                lockscreenSmartspace == null ||
+                launcherSmartspaceState == null
+        ) {
             return false
         }
 
@@ -1135,8 +1218,10 @@
         // element transition is if we're doing a biometric unlock. Otherwise, it means the bouncer
         // is showing, and you can't see the lockscreen smartspace, so a shared element transition
         // would not make sense.
-        if (!keyguardStateController.canDismissLockScreen() &&
-            !biometricUnlockControllerLazy.get().isBiometricUnlock) {
+        if (
+            !keyguardStateController.canDismissLockScreen() &&
+                !biometricUnlockControllerLazy.get().isBiometricUnlock
+        ) {
             return false
         }
 
@@ -1175,9 +1260,7 @@
         return willUnlockWithSmartspaceTransition
     }
 
-    /**
-     * Whether the RemoteAnimation on the app/launcher surface behind the keyguard is 'running'.
-     */
+    /** Whether the RemoteAnimation on the app/launcher surface behind the keyguard is 'running'. */
     fun isAnimatingBetweenKeyguardAndSurfaceBehind(): Boolean {
         return keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehind
     }
@@ -1196,39 +1279,38 @@
      * in-window/shared element transitions!
      */
     fun isSupportedLauncherUnderneath(): Boolean {
-        return launcherActivityClass?.let { ActivityManagerWrapper.getInstance()
-                .runningTask?.topActivity?.className?.equals(it) }
-                ?: false
+        return launcherActivityClass?.let {
+            ActivityManagerWrapper.getInstance().runningTask?.topActivity?.className?.equals(it)
+        } ?: false
     }
 
     /**
-     * Temporary method for b/298186160
-     * TODO (b/298186160) replace references with the constant itself when flag is removed
+     * Temporary method for b/298186160 TODO (b/298186160) replace references with the constant
+     * itself when flag is removed
      */
     private fun cannedUnlockStartDelayMs(): Long {
         return if (fasterUnlockTransition()) CANNED_UNLOCK_START_DELAY
-                else LEGACY_CANNED_UNLOCK_START_DELAY
+        else LEGACY_CANNED_UNLOCK_START_DELAY
     }
 
     /**
-     * Temporary method for b/298186160
-     * TODO (b/298186160) replace references with the constant itself when flag is removed
+     * Temporary method for b/298186160 TODO (b/298186160) replace references with the constant
+     * itself when flag is removed
      */
     private fun unlockAnimationDurationMs(): Long {
         return if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
-                else LEGACY_UNLOCK_ANIMATION_DURATION_MS
+        else LEGACY_UNLOCK_ANIMATION_DURATION_MS
     }
 
     /**
-     * Temporary method for b/298186160
-     * TODO (b/298186160) replace references with the constant itself when flag is removed
+     * Temporary method for b/298186160 TODO (b/298186160) replace references with the constant
+     * itself when flag is removed
      */
     private fun surfaceBehindFadeOutStartDelayMs(): Long {
         return if (fasterUnlockTransition()) UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
-                else LEGACY_UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
+        else LEGACY_UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
     }
 
-
     companion object {
 
         fun isFoldable(resources: Resources): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index e46a7cb..0b3d0f7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1278,6 +1278,8 @@
                             initAlphaForAnimationTargets(wallpapers);
                             if (isDream) {
                                 mDreamViewModel.get().startTransitionFromDream();
+                            } else {
+                                mCommunalTransitionViewModel.get().snapToCommunal();
                             }
                             mUnoccludeFinishedCallback = finishedCallback;
                             return;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
index 1c63235..3e6d5da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
@@ -247,6 +247,7 @@
         lp.height = ViewGroup.LayoutParams.MATCH_PARENT
         lp.width = ViewGroup.LayoutParams.MATCH_PARENT
         bgView.layoutParams = lp
+        bgView.alpha = 0f
     }
 
     fun getIconState(icon: IconType, aod: Boolean): IntArray {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 630dcca..15892e9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -25,7 +25,6 @@
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
@@ -35,93 +34,70 @@
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import com.android.systemui.util.kotlin.filterValuesNotNull
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.stateIn
 
 /** Models UI state and handles user input for the lockscreen scene. */
 @SysUISingleton
 class LockscreenSceneViewModel
 @Inject
 constructor(
-    @Application applicationScope: CoroutineScope,
-    deviceEntryInteractor: DeviceEntryInteractor,
-    communalInteractor: CommunalInteractor,
-    shadeInteractor: ShadeInteractor,
+    private val deviceEntryInteractor: DeviceEntryInteractor,
+    private val communalInteractor: CommunalInteractor,
+    private val shadeInteractor: ShadeInteractor,
     val touchHandling: KeyguardTouchHandlingViewModel,
     val notifications: NotificationsPlaceholderViewModel,
 ) {
-    val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
-        shadeInteractor.isShadeTouchable
-            .flatMapLatest { isShadeTouchable ->
-                if (!isShadeTouchable) {
-                    flowOf(emptyMap())
-                } else {
-                    combine(
-                        deviceEntryInteractor.isUnlocked,
-                        communalInteractor.isCommunalAvailable,
-                        shadeInteractor.shadeMode,
-                    ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
-                        destinationScenes(
-                            isDeviceUnlocked = isDeviceUnlocked,
-                            isCommunalAvailable = isCommunalAvailable,
-                            shadeMode = shadeMode,
+    val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+        shadeInteractor.isShadeTouchable.flatMapLatest { isShadeTouchable ->
+            if (!isShadeTouchable) {
+                flowOf(emptyMap())
+            } else {
+                combine(
+                    deviceEntryInteractor.isUnlocked,
+                    communalInteractor.isCommunalAvailable,
+                    shadeInteractor.shadeMode,
+                ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
+                    val notifShadeSceneKey =
+                        UserActionResult(
+                            toScene = SceneFamilies.NotifShade,
+                            transitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split },
                         )
-                    }
+
+                    mapOf(
+                            Swipe.Left to
+                                UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable },
+                            Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,
+
+                            // Swiping down from the top edge goes to QS (or shade if in split shade
+                            // mode).
+                            swipeDownFromTop(pointerCount = 1) to
+                                if (shadeMode is ShadeMode.Single) {
+                                    UserActionResult(Scenes.QuickSettings)
+                                } else {
+                                    notifShadeSceneKey
+                                },
+
+                            // TODO(b/338577208): Remove once we add Dual Shade invocation zones.
+                            swipeDownFromTop(pointerCount = 2) to
+                                UserActionResult(
+                                    toScene = SceneFamilies.QuickSettings,
+                                    transitionKey =
+                                        ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+                                ),
+
+                            // Swiping down, not from the edge, always navigates to the notif shade
+                            // scene.
+                            swipeDown(pointerCount = 1) to notifShadeSceneKey,
+                            swipeDown(pointerCount = 2) to notifShadeSceneKey,
+                        )
+                        .filterValuesNotNull()
                 }
             }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue =
-                    destinationScenes(
-                        isDeviceUnlocked = deviceEntryInteractor.isUnlocked.value,
-                        isCommunalAvailable = false,
-                        shadeMode = shadeInteractor.shadeMode.value,
-                    ),
-            )
-
-    private fun destinationScenes(
-        isDeviceUnlocked: Boolean,
-        isCommunalAvailable: Boolean,
-        shadeMode: ShadeMode,
-    ): Map<UserAction, UserActionResult> {
-        val notifShadeSceneKey =
-            UserActionResult(
-                toScene = SceneFamilies.NotifShade,
-                transitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split },
-            )
-
-        return mapOf(
-                Swipe.Left to UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable },
-                Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,
-
-                // Swiping down from the top edge goes to QS (or shade if in split shade mode).
-                swipeDownFromTop(pointerCount = 1) to
-                    if (shadeMode is ShadeMode.Single) {
-                        UserActionResult(Scenes.QuickSettings)
-                    } else {
-                        notifShadeSceneKey
-                    },
-
-                // TODO(b/338577208): Remove once we add Dual Shade invocation zones.
-                swipeDownFromTop(pointerCount = 2) to
-                    UserActionResult(
-                        toScene = SceneFamilies.QuickSettings,
-                        transitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
-                    ),
-
-                // Swiping down, not from the edge, always navigates to the notif shade scene.
-                swipeDown(pointerCount = 1) to notifShadeSceneKey,
-                swipeDown(pointerCount = 2) to notifShadeSceneKey,
-            )
-            .filterValuesNotNull()
-    }
+        }
 
     private fun swipeDownFromTop(pointerCount: Int): Swipe {
         return Swipe(
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 6c53374..cc4a92c 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -187,70 +187,40 @@
             }
         }
 
-        CharSequence dialogText = null;
-        CharSequence dialogTitle = null;
-
         final String appName = extractAppName(aInfo, packageManager);
         final boolean hasCastingCapabilities =
                 Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName);
 
-        if (hasCastingCapabilities) {
-            dialogText = getString(R.string.media_projection_sys_service_dialog_warning);
-            dialogTitle = getString(R.string.media_projection_sys_service_dialog_title);
-        } else {
-            String actionText = getString(R.string.media_projection_dialog_warning, appName);
-            SpannableString message = new SpannableString(actionText);
-
-            int appNameIndex = actionText.indexOf(appName);
-            if (appNameIndex >= 0) {
-                message.setSpan(new StyleSpan(Typeface.BOLD),
-                        appNameIndex, appNameIndex + appName.length(), 0);
-            }
-            dialogText = message;
-            dialogTitle = getString(R.string.media_projection_dialog_title, appName);
-        }
-
         // Using application context for the dialog, instead of the activity context, so we get
         // the correct screen width when in split screen.
         Context dialogContext = getApplicationContext();
-        if (isPartialScreenSharingEnabled()) {
-            final boolean overrideDisableSingleAppOption =
-                    CompatChanges.isChangeEnabled(
-                            OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
-                            mPackageName, getHostUserHandle());
-            MediaProjectionPermissionDialogDelegate delegate =
-                    new MediaProjectionPermissionDialogDelegate(
-                            dialogContext,
-                            getMediaProjectionConfig(),
-                            dialog -> {
-                                ScreenShareOption selectedOption =
-                                        dialog.getSelectedScreenShareOption();
-                                grantMediaProjectionPermission(selectedOption.getMode());
-                            },
-                            () -> finish(RECORD_CANCEL, /* projection= */ null),
-                            hasCastingCapabilities,
-                            appName,
-                            overrideDisableSingleAppOption,
-                            mUid,
-                            mMediaProjectionMetricsLogger);
-            mDialog =
-                    new AlertDialogWithDelegate(
-                            dialogContext, R.style.Theme_SystemUI_Dialog, delegate);
-        } else {
-            AlertDialog.Builder dialogBuilder =
-                    new AlertDialog.Builder(dialogContext, R.style.Theme_SystemUI_Dialog)
-                            .setTitle(dialogTitle)
-                            .setIcon(R.drawable.ic_media_projection_permission)
-                            .setMessage(dialogText)
-                            .setPositiveButton(R.string.media_projection_action_text, this)
-                            .setNeutralButton(android.R.string.cancel, this);
-            mDialog = dialogBuilder.create();
-        }
+        final boolean overrideDisableSingleAppOption =
+                CompatChanges.isChangeEnabled(
+                        OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
+                        mPackageName, getHostUserHandle());
+        MediaProjectionPermissionDialogDelegate delegate =
+                new MediaProjectionPermissionDialogDelegate(
+                        dialogContext,
+                        getMediaProjectionConfig(),
+                        dialog -> {
+                            ScreenShareOption selectedOption =
+                                    dialog.getSelectedScreenShareOption();
+                            grantMediaProjectionPermission(selectedOption.getMode());
+                        },
+                        () -> finish(RECORD_CANCEL, /* projection= */ null),
+                        hasCastingCapabilities,
+                        appName,
+                        overrideDisableSingleAppOption,
+                        mUid,
+                        mMediaProjectionMetricsLogger);
+        mDialog =
+                new AlertDialogWithDelegate(
+                        dialogContext, R.style.Theme_SystemUI_Dialog, delegate);
 
         if (savedInstanceState == null) {
             mMediaProjectionMetricsLogger.notifyProjectionInitiated(
                     mUid,
-                    appName == null
+                    hasCastingCapabilities
                             ? SessionCreationSource.CAST
                             : SessionCreationSource.APP);
         }
@@ -366,7 +336,7 @@
                 setResult(RESULT_OK, intent);
                 finish(RECORD_CONTENT_DISPLAY, projection);
             }
-            if (isPartialScreenSharingEnabled() && screenShareMode == SINGLE_APP) {
+            if (screenShareMode == SINGLE_APP) {
                 IMediaProjection projection = MediaProjectionServiceHelper.createOrReuseProjection(
                         mUid, mPackageName, mReviewGrantedConsentRequired);
                 final Intent intent = new Intent(this,
@@ -437,8 +407,4 @@
         return intent.getParcelableExtra(
                 MediaProjectionManager.EXTRA_MEDIA_PROJECTION_CONFIG);
     }
-
-    private boolean isPartialScreenSharingEnabled() {
-        return mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
index 1f74716..fa8e13a 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
@@ -25,9 +25,8 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeAlignment
 import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
 
 /** Models UI state and handles user input for the Notifications Shade scene. */
 @SysUISingleton
@@ -36,16 +35,15 @@
 constructor(
     shadeInteractor: ShadeInteractor,
 ) {
-    val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
-        MutableStateFlow(
-                mapOf(
-                    if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
-                        Swipe.Up
-                    } else {
-                        Swipe.Down
-                    } to SceneFamilies.Home,
-                    Back to SceneFamilies.Home,
-                )
+    val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+        flowOf(
+            mapOf(
+                if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
+                    Swipe.Up
+                } else {
+                    Swipe.Down
+                } to SceneFamilies.Home,
+                Back to SceneFamilies.Home,
             )
-            .asStateFlow()
+        )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
index 083bf05..4c6563d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
@@ -16,10 +16,14 @@
 
 package com.android.systemui.qs.tiles.impl.modes.domain.interactor
 
+//noinspection CleanArchitectureDependencyViolation: dialog needs to be opened on click
 import android.content.Intent
 import android.provider.Settings
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.animation.Expandable
-import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
 import com.android.systemui.qs.tiles.base.interactor.QSTileInput
 import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
@@ -27,13 +31,15 @@
 import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
 import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
 import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
 
-@SysUISingleton
 class ModesTileUserActionInteractor
 @Inject
 constructor(
-    private val qsTileIntentUserInputHandler: QSTileIntentUserInputHandler,
-    // TODO(b/353896370): The domain layer should not have to depend on the UI layer.
+    @Main private val coroutineContext: CoroutineContext,
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+    private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val dialogDelegate: ModesDialogDelegate,
 ) : QSTileUserActionInteractor<ModesTileModel> {
     val longClickIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
@@ -45,14 +51,29 @@
                     handleClick(action.expandable)
                 }
                 is QSTileUserAction.LongClick -> {
-                    qsTileIntentUserInputHandler.handle(action.expandable, longClickIntent)
+                    qsTileIntentUserActionHandler.handle(action.expandable, longClickIntent)
                 }
             }
         }
     }
 
     suspend fun handleClick(expandable: Expandable?) {
-        // Show a dialog with the list of modes to configure.
-        dialogDelegate.showDialog(expandable)
+        // Show a dialog with the list of modes to configure. Dialogs shown by the
+        // DialogTransitionAnimator must be created and shown on the main thread, so we post it to
+        // the UI handler.
+        withContext(coroutineContext) {
+            val dialog = dialogDelegate.createDialog()
+
+            expandable
+                ?.dialogTransitionController(
+                    DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
+                )
+                ?.let { controller -> dialogTransitionAnimator.show(dialog, controller) }
+                ?: dialog.show()
+        }
+    }
+
+    companion object {
+        private const val INTERACTION_JANK_TAG = "configure_priority_modes"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 79cdfec..b1cc55d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -25,7 +25,6 @@
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
 import com.android.systemui.qs.FooterActionsController
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -38,20 +37,17 @@
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
 
 /** Models UI state and handles user input for the quick settings scene. */
 @SysUISingleton
 class QuickSettingsSceneViewModel
 @Inject
 constructor(
-    @Application private val applicationScope: CoroutineScope,
     val brightnessMirrorViewModel: BrightnessMirrorViewModel,
     val shadeHeaderViewModel: ShadeHeaderViewModel,
     val qsSceneAdapter: QSSceneAdapter,
@@ -61,55 +57,35 @@
     sceneBackInteractor: SceneBackInteractor,
     val mediaCarouselInteractor: MediaCarouselInteractor,
 ) {
-    private val backScene: StateFlow<SceneKey> =
+    private val backScene: Flow<SceneKey> =
         sceneBackInteractor.backScene
             .filter { it != Scenes.QuickSettings }
             .map { it ?: Scenes.Shade }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = Scenes.Shade,
-            )
 
-    val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+    val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
         combine(
-                qsSceneAdapter.isCustomizerShowing,
-                backScene,
-                transform = ::destinationScenes,
-            )
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue =
-                    destinationScenes(
-                        isCustomizing = qsSceneAdapter.isCustomizerShowing.value,
-                        backScene = backScene.value,
-                    ),
-            )
+            qsSceneAdapter.isCustomizerShowing,
+            backScene,
+        ) { isCustomizing, backScene ->
+            buildMap<UserAction, UserActionResult> {
+                if (isCustomizing) {
+                    // TODO(b/332749288) Empty map so there are no back handlers and back can close
+                    // customizer
 
-    val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
-
-    private fun destinationScenes(
-        isCustomizing: Boolean,
-        backScene: SceneKey?,
-    ): Map<UserAction, UserActionResult> {
-        return buildMap {
-            if (isCustomizing) {
-                // TODO(b/332749288) Empty map so there are no back handlers and back can close
-                // customizer
-
-                // TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade
-                // while customizing
-            } else {
-                put(Back, UserActionResult(backScene ?: Scenes.Shade))
-                put(Swipe(SwipeDirection.Up), UserActionResult(backScene ?: Scenes.Shade))
-                put(
-                    Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up),
-                    UserActionResult(SceneFamilies.Home),
-                )
+                    // TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade
+                    // while customizing
+                } else {
+                    put(Back, UserActionResult(backScene))
+                    put(Swipe(SwipeDirection.Up), UserActionResult(backScene))
+                    put(
+                        Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up),
+                        UserActionResult(SceneFamilies.Home),
+                    )
+                }
             }
         }
-    }
+
+    val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
 
     private val footerActionsControllerInitialized = AtomicBoolean(false)
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
index 66fcbac..e012f2c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
@@ -21,17 +21,13 @@
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeAlignment
 import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
 
 /** Models UI state and handles user input for the Quick Settings Shade scene. */
 @SysUISingleton
@@ -41,33 +37,22 @@
     private val shadeInteractor: ShadeInteractor,
     val overlayShadeViewModel: OverlayShadeViewModel,
     val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
-    @Application applicationScope: CoroutineScope,
 ) {
 
-    val isEditing: StateFlow<Boolean> = quickSettingsContainerViewModel.editModeViewModel.isEditing
-
-    val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
-        isEditing
-            .map { editing -> destinations(editing) }
-            .stateIn(
-                applicationScope,
-                SharingStarted.WhileSubscribed(),
-                destinations(isEditing.value)
-            )
-
-    private fun destinations(editing: Boolean): Map<UserAction, UserActionResult> {
-        return buildMap {
-            put(
-                if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
-                    Swipe.Up
-                } else {
-                    Swipe.Down
-                },
-                UserActionResult(SceneFamilies.Home)
-            )
-            if (!editing) {
-                put(Back, UserActionResult(SceneFamilies.Home))
+    val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+        quickSettingsContainerViewModel.editModeViewModel.isEditing.map { editing ->
+            buildMap {
+                put(
+                    if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
+                        Swipe.Up
+                    } else {
+                        Swipe.Down
+                    },
+                    UserActionResult(SceneFamilies.Home)
+                )
+                if (!editing) {
+                    put(Back, UserActionResult(SceneFamilies.Home))
+                }
             }
         }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index dd2dbf3..f8b3ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -35,7 +35,6 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
 import com.android.systemui.flags.Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES
 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
 import com.android.systemui.mediaprojection.SessionCreationSource
@@ -154,10 +153,7 @@
             SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
         )
 
-        if (
-            flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING) &&
-                !state.hasUserApprovedScreenRecording
-        ) {
+        if (!state.hasUserApprovedScreenRecording) {
             mainExecutor.execute {
                 ScreenCapturePermissionDialogDelegate(factory, state).createDialog().apply {
                     setOnCancelListener { screenRecordSwitch.isChecked = false }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
index 939d5bc..c7190c3 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
@@ -19,7 +19,8 @@
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
-import kotlinx.coroutines.flow.StateFlow
+import com.android.systemui.activatable.Activatable
+import kotlinx.coroutines.flow.Flow
 
 /**
  * Defines interface for classes that can describe a "scene".
@@ -29,11 +30,13 @@
  * based on either user action (for example, swiping down while on the lock screen scene may switch
  * to the shade scene).
  */
-interface Scene {
+interface Scene : Activatable {
 
     /** Uniquely-identifying key for this scene. The key must be unique within its container. */
     val key: SceneKey
 
+    override suspend fun activate() = Unit
+
     /**
      * The mapping between [UserAction] and destination [UserActionResult]s.
      *
@@ -54,5 +57,5 @@
      * type is not currently active in the scene and should be ignored by the framework, while the
      * current scene is this one.
      */
-    val destinationScenes: StateFlow<Map<UserAction, UserActionResult>>
+    val destinationScenes: Flow<Map<UserAction, UserActionResult>>
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index c20d577..d31d6f4 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -103,6 +103,7 @@
                                 windowInsets = windowInsets,
                                 sceneByKey = sortedSceneByKey,
                                 dataSourceDelegator = dataSourceDelegator,
+                                containerConfig = containerConfig,
                             )
                             .also { it.id = R.id.scene_container_root_composable }
                     )
@@ -141,6 +142,7 @@
         windowInsets: StateFlow<WindowInsets?>,
         sceneByKey: Map<SceneKey, Scene>,
         dataSourceDelegator: SceneDataSourceDelegator,
+        containerConfig: SceneContainerConfig,
     ): View {
         return ComposeView(context).apply {
             setContent {
@@ -153,6 +155,7 @@
                             viewModel = viewModel,
                             sceneByKey =
                                 sceneByKey.mapValues { (_, scene) -> scene as ComposableScene },
+                            initialSceneKey = containerConfig.initialSceneKey,
                             dataSourceDelegator = dataSourceDelegator,
                         )
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
index 9f48ee9..b739ffe 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
@@ -23,79 +23,61 @@
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.TransitionKeys.OpenBottomShade
 import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
 
 @SysUISingleton
 class GoneSceneViewModel
 @Inject
 constructor(
-    @Application private val applicationScope: CoroutineScope,
     private val shadeInteractor: ShadeInteractor,
 ) {
-    val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
-        shadeInteractor.shadeMode
-            .map(::destinationScenes)
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue =
-                    destinationScenes(
-                        shadeMode = shadeInteractor.shadeMode.value,
-                    )
-            )
+    val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+        shadeInteractor.shadeMode.map { shadeMode ->
+            buildMap {
+                if (
+                    shadeMode is ShadeMode.Single ||
+                        // TODO(b/338577208): Remove this once we add Dual Shade invocation zones.
+                        shadeMode is ShadeMode.Dual
+                ) {
+                    if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) {
+                        put(
+                            Swipe(
+                                pointerCount = 2,
+                                fromSource = Edge.Bottom,
+                                direction = SwipeDirection.Up,
+                            ),
+                            UserActionResult(SceneFamilies.QuickSettings, OpenBottomShade)
+                        )
+                    } else {
+                        put(
+                            Swipe(
+                                pointerCount = 2,
+                                fromSource = Edge.Top,
+                                direction = SwipeDirection.Down,
+                            ),
+                            UserActionResult(SceneFamilies.QuickSettings)
+                        )
+                    }
+                }
 
-    private fun destinationScenes(
-        shadeMode: ShadeMode,
-    ): Map<UserAction, UserActionResult> {
-        return buildMap {
-            if (
-                shadeMode is ShadeMode.Single ||
-                    // TODO(b/338577208): Remove this once we add Dual Shade invocation zones.
-                    shadeMode is ShadeMode.Dual
-            ) {
                 if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) {
-                    put(
-                        Swipe(
-                            pointerCount = 2,
-                            fromSource = Edge.Bottom,
-                            direction = SwipeDirection.Up,
-                        ),
-                        UserActionResult(SceneFamilies.QuickSettings, OpenBottomShade)
-                    )
+                    put(Swipe.Up, UserActionResult(SceneFamilies.NotifShade, OpenBottomShade))
                 } else {
                     put(
-                        Swipe(
-                            pointerCount = 2,
-                            fromSource = Edge.Top,
-                            direction = SwipeDirection.Down,
-                        ),
-                        UserActionResult(SceneFamilies.QuickSettings)
+                        Swipe.Down,
+                        UserActionResult(
+                            SceneFamilies.NotifShade,
+                            ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+                        )
                     )
                 }
             }
-
-            if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) {
-                put(Swipe.Up, UserActionResult(SceneFamilies.NotifShade, OpenBottomShade))
-            } else {
-                put(
-                    Swipe.Down,
-                    UserActionResult(
-                        SceneFamilies.NotifShade,
-                        ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
-                    )
-                )
-            }
         }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 46c5861..a8a78a9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -171,11 +171,8 @@
         mMediaProjectionMetricsLogger.notifyProjectionInitiated(
                 getHostUid(), SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
 
-        return (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
-                ? mScreenRecordPermissionDialogDelegateFactory
-                    .create(this, getHostUserHandle(), getHostUid(), onStartRecordingClicked)
-                : mScreenRecordDialogFactory
-                    .create(this, onStartRecordingClicked))
+        return mScreenRecordPermissionDialogDelegateFactory
+                .create(this, getHostUserHandle(), getHostUid(), onStartRecordingClicked)
                 .createDialog();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index b468d0e..25d1cd1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -39,6 +39,7 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.compose.theme.PlatformTheme
 import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.Flags
 import com.android.systemui.Flags.glanceableHubBackGesture
 import com.android.systemui.ambient.touch.TouchMonitor
 import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
@@ -294,24 +295,37 @@
                     }
 
                 containerView.systemGestureExclusionRects =
-                    listOf(
-                        // Only allow swipe up to bouncer and swipe down to shade in the very
-                        // top/bottom to avoid conflicting with widgets in the hub grid.
-                        Rect(
-                            insets.left,
-                            topEdgeSwipeRegionWidth,
-                            containerView.right - insets.right,
-                            containerView.bottom - bottomEdgeSwipeRegionWidth
-                        ),
-                        // Disable back gestures on the left side of the screen, to avoid
-                        // conflicting with scene transitions.
-                        Rect(
-                            0,
-                            0,
-                            insets.right,
-                            containerView.bottom,
+                    if (Flags.hubmodeFullscreenVerticalSwipe()) {
+                        listOf(
+                            // Disable back gestures on the left side of the screen, to avoid
+                            // conflicting with scene transitions.
+                            Rect(
+                                0,
+                                0,
+                                insets.right,
+                                containerView.bottom,
+                            )
                         )
-                    )
+                    } else {
+                        listOf(
+                            // Only allow swipe up to bouncer and swipe down to shade in the very
+                            // top/bottom to avoid conflicting with widgets in the hub grid.
+                            Rect(
+                                insets.left,
+                                topEdgeSwipeRegionWidth,
+                                containerView.right - insets.right,
+                                containerView.bottom - bottomEdgeSwipeRegionWidth
+                            ),
+                            // Disable back gestures on the left side of the screen, to avoid
+                            // conflicting with scene transitions.
+                            Rect(
+                                0,
+                                0,
+                                insets.right,
+                                containerView.bottom,
+                            )
+                        )
+                    }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 2b2aac64..f90dd3c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -24,8 +24,8 @@
 import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.activatable.Activatable
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
 import com.android.systemui.qs.FooterActionsController
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -41,22 +41,23 @@
 import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /** Models UI state and handles user input for the shade scene. */
 @SysUISingleton
 class ShadeSceneViewModel
 @Inject
 constructor(
-    @Application private val applicationScope: CoroutineScope,
     val qsSceneAdapter: QSSceneAdapter,
     val shadeHeaderViewModel: ShadeHeaderViewModel,
     val brightnessMirrorViewModel: BrightnessMirrorViewModel,
@@ -66,42 +67,61 @@
     private val footerActionsController: FooterActionsController,
     private val sceneInteractor: SceneInteractor,
     private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
-) {
-    val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+) : Activatable {
+    val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
         combine(
-                shadeInteractor.shadeMode,
-                qsSceneAdapter.isCustomizerShowing,
-            ) { shadeMode, isCustomizerShowing ->
-                destinationScenes(
-                    shadeMode = shadeMode,
-                    isCustomizing = isCustomizerShowing,
-                )
+            shadeInteractor.shadeMode,
+            qsSceneAdapter.isCustomizerShowing,
+        ) { shadeMode, isCustomizerShowing ->
+            buildMap {
+                if (!isCustomizerShowing) {
+                    set(
+                        Swipe(SwipeDirection.Up),
+                        UserActionResult(
+                            SceneFamilies.Home,
+                            ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+                        )
+                    )
+                }
+
+                // TODO(b/330200163) Add an else to be able to collapse the shade while customizing
+                if (shadeMode is ShadeMode.Single) {
+                    set(Swipe(SwipeDirection.Down), UserActionResult(Scenes.QuickSettings))
+                }
             }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue =
-                    destinationScenes(
-                        shadeMode = shadeInteractor.shadeMode.value,
-                        isCustomizing = qsSceneAdapter.isCustomizerShowing.value,
-                    ),
-            )
+        }
 
     private val upDestinationSceneKey: Flow<SceneKey?> =
         destinationScenes.map { it[Swipe(SwipeDirection.Up)]?.toScene }
 
+    private val _isClickable = MutableStateFlow(false)
     /** Whether or not the shade container should be clickable. */
-    val isClickable: StateFlow<Boolean> =
-        upDestinationSceneKey
-            .flatMapLatestConflated { key ->
-                key?.let { sceneInteractor.resolveSceneFamily(key) } ?: flowOf(null)
+    val isClickable: StateFlow<Boolean> = _isClickable.asStateFlow()
+
+    /**
+     * Activates the view-model.
+     *
+     * Serves as an entrypoint to kick off coroutine work that the view-model requires in order to
+     * keep its state fresh and/or perform side-effects.
+     *
+     * Suspends the caller forever as it will keep doing work until canceled.
+     *
+     * **Must be invoked** when the scene becomes the current scene or when it becomes visible
+     * during a transition (the choice is the responsibility of the parent). Similarly, the work
+     * must be canceled when the scene stops being visible or the current scene.
+     */
+    override suspend fun activate() {
+        coroutineScope {
+            launch {
+                upDestinationSceneKey
+                    .flatMapLatestConflated { key ->
+                        key?.let { sceneInteractor.resolveSceneFamily(key) } ?: flowOf(null)
+                    }
+                    .map { it == Scenes.Lockscreen }
+                    .collectLatest { _isClickable.value = it }
             }
-            .map { it == Scenes.Lockscreen }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = false
-            )
+        }
+    }
 
     val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
 
@@ -132,24 +152,4 @@
         }
         return footerActionsViewModelFactory.create(lifecycleOwner)
     }
-
-    private fun destinationScenes(
-        shadeMode: ShadeMode,
-        isCustomizing: Boolean,
-    ): Map<UserAction, UserActionResult> {
-        return buildMap {
-            if (!isCustomizing) {
-                set(
-                    Swipe(SwipeDirection.Up),
-                    UserActionResult(
-                        SceneFamilies.Home,
-                        ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
-                    )
-                )
-            } // TODO(b/330200163) Add an else to be able to collapse the shade while customizing
-            if (shadeMode is ShadeMode.Single) {
-                set(Swipe(SwipeDirection.Down), UserActionResult(Scenes.QuickSettings))
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
index a6605f6..a621b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
@@ -18,12 +18,9 @@
 
 import android.annotation.SuppressLint
 import android.app.NotificationManager
-import android.os.UserHandle
-import android.provider.Settings
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -43,23 +40,16 @@
 import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_UNSEEN
 import com.android.systemui.util.asIndenting
 import com.android.systemui.util.printCollection
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import java.io.PrintWriter
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.conflate
-import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 
 /**
@@ -73,12 +63,10 @@
 class LockScreenMinimalismCoordinator
 @Inject
 constructor(
-    @Background private val bgDispatcher: CoroutineDispatcher,
     private val dumpManager: DumpManager,
     private val headsUpInteractor: HeadsUpNotificationInteractor,
     private val logger: LockScreenMinimalismCoordinatorLogger,
     @Application private val scope: CoroutineScope,
-    private val secureSettings: SecureSettings,
     private val seenNotificationsInteractor: SeenNotificationsInteractor,
     private val statusBarStateController: StatusBarStateController,
     private val shadeInteractor: ShadeInteractor,
@@ -147,29 +135,7 @@
         if (NotificationMinimalismPrototype.isEnabled) {
             return flowOf(true)
         }
-        return secureSettings
-            // emit whenever the setting has changed
-            .observerFlow(
-                UserHandle.USER_ALL,
-                Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-            )
-            // perform a query immediately
-            .onStart { emit(Unit) }
-            // for each change, lookup the new value
-            .map {
-                secureSettings.getIntForUser(
-                    name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-                    def = 0,
-                    userHandle = UserHandle.USER_CURRENT,
-                ) == 1
-            }
-            // don't emit anything if nothing has changed
-            .distinctUntilChanged()
-            // perform lookups on the bg thread pool
-            .flowOn(bgDispatcher)
-            // only track the most recent emission, if events are happening faster than they can be
-            // consumed
-            .conflate()
+        return seenNotificationsInteractor.isLockScreenShowOnlyUnseenNotificationsEnabled()
     }
 
     private suspend fun trackUnseenFilterSettingChanges() {
@@ -177,6 +143,7 @@
             // update local field and invalidate if necessary
             if (isSettingEnabled != unseenFilterEnabled) {
                 unseenFilterEnabled = isSettingEnabled
+                unseenNotifications.clear()
                 unseenNotifPromoter.invalidateList("unseen setting changed")
             }
             // if the setting is enabled, then start tracking and filtering unseen notifications
@@ -190,21 +157,21 @@
     private val collectionListener =
         object : NotifCollectionListener {
             override fun onEntryAdded(entry: NotificationEntry) {
-                if (!isShadeVisible) {
+                if (unseenFilterEnabled && !isShadeVisible) {
                     logger.logUnseenAdded(entry.key)
                     unseenNotifications.add(entry)
                 }
             }
 
             override fun onEntryUpdated(entry: NotificationEntry) {
-                if (!isShadeVisible) {
+                if (unseenFilterEnabled && !isShadeVisible) {
                     logger.logUnseenUpdated(entry.key)
                     unseenNotifications.add(entry)
                 }
             }
 
             override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
-                if (unseenNotifications.remove(entry)) {
+                if (unseenFilterEnabled && unseenNotifications.remove(entry)) {
                     logger.logUnseenRemoved(entry.key)
                 }
             }
@@ -212,6 +179,7 @@
 
     private fun pickOutTopUnseenNotifs(list: List<ListEntry>) {
         if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return
+        if (!unseenFilterEnabled) return
         // Only ever elevate a top unseen notification on keyguard, not even locked shade
         if (statusBarStateController.state != StatusBarState.KEYGUARD) {
             seenNotificationsInteractor.setTopOngoingNotification(null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
index 0103fff..bfea2ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
@@ -17,12 +17,9 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.annotation.SuppressLint
-import android.os.UserHandle
-import android.provider.Settings
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -43,12 +40,9 @@
 import com.android.systemui.statusbar.policy.headsUpEvents
 import com.android.systemui.util.asIndenting
 import com.android.systemui.util.indentIfPossible
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import java.io.PrintWriter
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.coroutineScope
@@ -56,13 +50,10 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.yield
 
@@ -79,14 +70,12 @@
 class OriginalUnseenKeyguardCoordinator
 @Inject
 constructor(
-    @Background private val bgDispatcher: CoroutineDispatcher,
     private val dumpManager: DumpManager,
     private val headsUpManager: HeadsUpManager,
     private val keyguardRepository: KeyguardRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val logger: KeyguardCoordinatorLogger,
     @Application private val scope: CoroutineScope,
-    private val secureSettings: SecureSettings,
     private val seenNotificationsInteractor: SeenNotificationsInteractor,
     private val statusBarStateController: StatusBarStateController,
     private val sceneInteractor: SceneInteractor,
@@ -268,29 +257,7 @@
             // TODO(b/330387368): should this really just be turned off? If so, hide the setting.
             return flowOf(false)
         }
-        return secureSettings
-            // emit whenever the setting has changed
-            .observerFlow(
-                UserHandle.USER_ALL,
-                Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-            )
-            // perform a query immediately
-            .onStart { emit(Unit) }
-            // for each change, lookup the new value
-            .map {
-                secureSettings.getIntForUser(
-                    name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-                    def = 0,
-                    userHandle = UserHandle.USER_CURRENT,
-                ) == 1
-            }
-            // don't emit anything if nothing has changed
-            .distinctUntilChanged()
-            // perform lookups on the bg thread pool
-            .flowOn(bgDispatcher)
-            // only track the most recent emission, if events are happening faster than they can be
-            // consumed
-            .conflate()
+        return seenNotificationsInteractor.isLockScreenShowOnlyUnseenNotificationsEnabled()
     }
 
     private suspend fun trackUnseenFilterSettingChanges() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
index 948a3c2..90a05ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
@@ -16,21 +16,35 @@
 
 package com.android.systemui.statusbar.notification.domain.interactor
 
+import android.os.UserHandle
+import android.provider.Settings
 import android.util.IndentingPrintWriter
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
 import com.android.systemui.util.printSection
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 
 /** Interactor for business logic associated with the notification stack. */
 @SysUISingleton
 class SeenNotificationsInteractor
 @Inject
 constructor(
+    @Background private val bgDispatcher: CoroutineDispatcher,
     private val notificationListRepository: ActiveNotificationListRepository,
+    private val secureSettings: SecureSettings,
 ) {
     /** Are any already-seen notifications currently filtered out of the shade? */
     val hasFilteredOutSeenNotifications: StateFlow<Boolean> =
@@ -81,4 +95,29 @@
                 )
             }
         }
+
+    fun isLockScreenShowOnlyUnseenNotificationsEnabled(): Flow<Boolean> =
+        secureSettings
+            // emit whenever the setting has changed
+            .observerFlow(
+                UserHandle.USER_ALL,
+                Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+            )
+            // perform a query immediately
+            .onStart { emit(Unit) }
+            // for each change, lookup the new value
+            .map {
+                secureSettings.getIntForUser(
+                    name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+                    def = 0,
+                    userHandle = UserHandle.USER_CURRENT,
+                ) == 1
+            }
+            // don't emit anything if nothing has changed
+            .distinctUntilChanged()
+            // perform lookups on the bg thread pool
+            .flowOn(bgDispatcher)
+            // only track the most recent emission, if events are happening faster than they can be
+            // consumed
+            .conflate()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index e04e0fa..9d09595 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -389,6 +389,7 @@
                 .setTicker(titleStr)
                 .setContentTitle(titleStr)
                 .setContentText(textStr)
+                .setStyle(Notification.BigTextStyle().bigText(textStr))
                 .setSmallIcon(com.android.systemui.res.R.drawable.ic_settings)
                 .setCategory(Notification.CATEGORY_SYSTEM)
                 .setTimeoutAfter(/* one day in ms */ 24 * 60 * 60 * 1000L)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 8f2ad40..41b69a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -802,7 +802,10 @@
         hideAlternateBouncer(false);
         if (mKeyguardStateController.isShowing() && !isBouncerShowing()) {
             if (SceneContainerFlag.isEnabled()) {
-                mDeviceEntryInteractorLazy.get().attemptDeviceEntry();
+                mSceneInteractorLazy.get().changeScene(
+                        Scenes.Bouncer,
+                        "primary bouncer requested"
+                );
             } else {
                 mPrimaryBouncerInteractor.show(scrimmed);
             }
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 a88c6d7..a963826 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -38,9 +38,10 @@
 class AvalancheController
 @Inject
 constructor(
-    dumpManager: DumpManager,
-    private val uiEventLogger: UiEventLogger,
-    @Background private val bgHandler: Handler
+        dumpManager: DumpManager,
+        private val uiEventLogger: UiEventLogger,
+        private val headsUpManagerLogger: HeadsUpManagerLogger,
+        @Background private val bgHandler: Handler
 ) : Dumpable {
 
     private val tag = "AvalancheController"
@@ -109,32 +110,36 @@
     }
 
     /** Run or delay Runnable for given HeadsUpEntry */
-    fun update(entry: HeadsUpEntry?, runnable: Runnable?, label: String) {
+    fun update(entry: HeadsUpEntry?, runnable: Runnable?, caller: String) {
+        val isEnabled = isEnabled()
+        val key = getKey(entry)
+
         if (runnable == null) {
-            log { "Runnable is NULL, stop update." }
+            headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, "Runnable NULL, stop")
             return
         }
-        if (!isEnabled()) {
+        if (!isEnabled) {
+            headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key,
+                    "NOT ENABLED, run runnable")
             runnable.run()
             return
         }
-        log { "\n " }
-        val fn = "$label => AvalancheController.update ${getKey(entry)}"
         if (entry == null) {
-            log { "Entry is NULL, stop update." }
+            headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, "Entry NULL, stop")
             return
         }
         if (debug) {
-            debugRunnableLabelMap[runnable] = label
+            debugRunnableLabelMap[runnable] = caller
         }
+        var outcome = ""
         if (isShowing(entry)) {
-            log { "\n$fn => update showing" }
+            outcome = "update showing"
             runnable.run()
         } else if (entry in nextMap) {
-            log { "\n$fn => update next" }
+            outcome = "update next"
             nextMap[entry]?.add(runnable)
         } else if (headsUpEntryShowing == null) {
-            log { "\n$fn => showNow" }
+            outcome = "show now"
             showNow(entry, arrayListOf(runnable))
         } else {
             // Clean up invalid state when entry is in list but not map and vice versa
@@ -156,7 +161,8 @@
                 )
             }
         }
-        logState("after $fn")
+        outcome += getStateStr()
+        headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, outcome)
     }
 
     @VisibleForTesting
@@ -169,32 +175,37 @@
      * Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete
      * all Runnables associated with that entry.
      */
-    fun delete(entry: HeadsUpEntry?, runnable: Runnable?, label: String) {
+    fun delete(entry: HeadsUpEntry?, runnable: Runnable?, caller: String) {
+        val isEnabled = isEnabled()
+        val key = getKey(entry)
+
         if (runnable == null) {
-            log { "Runnable is NULL, stop delete." }
+            headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key, "Runnable NULL, stop")
             return
         }
-        if (!isEnabled()) {
+        if (!isEnabled) {
+            headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key,
+                    "NOT ENABLED, run runnable")
             runnable.run()
             return
         }
-        log { "\n " }
-        val fn = "$label => AvalancheController.delete " + getKey(entry)
         if (entry == null) {
-            log { "$fn => entry NULL, running runnable" }
+            headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key,
+                    "Entry NULL, run runnable")
             runnable.run()
             return
         }
+        var outcome = ""
         if (entry in nextMap) {
-            log { "$fn => remove from next" }
+            outcome = "remove from next"
             if (entry in nextMap) nextMap.remove(entry)
             if (entry in nextList) nextList.remove(entry)
             uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_REMOVED)
         } else if (entry in debugDropSet) {
-            log { "$fn => remove from dropset" }
+            outcome = "remove from dropset"
             debugDropSet.remove(entry)
         } else if (isShowing(entry)) {
-            log { "$fn => remove showing ${getKey(entry)}" }
+            outcome = "remove showing"
             previousHunKey = getKey(headsUpEntryShowing)
             // Show the next HUN before removing this one, so that we don't tell listeners
             // onHeadsUpPinnedModeChanged, which causes
@@ -203,10 +214,10 @@
             showNext()
             runnable.run()
         } else {
-            log { "$fn => run runnable for untracked shown ${getKey(entry)}" }
+            outcome = "run runnable for untracked shown"
             runnable.run()
         }
-        logState("after $fn")
+        headsUpManagerLogger.logAvalancheDelete(caller, isEnabled(), getKey(entry), outcome)
     }
 
     /**
@@ -384,23 +395,12 @@
     }
 
     private fun getStateStr(): String {
-        return "SHOWING: [${getKey(headsUpEntryShowing)}]" +
-            "\nPREVIOUS: [$previousHunKey]" +
-            "\nNEXT LIST: $nextListStr" +
-            "\nNEXT MAP: $nextMapStr" +
-            "\nDROPPED: $dropSetStr" +
-            "\nENABLED: $enableAtRuntime"
-    }
-
-    private fun logState(reason: String) {
-        log {
-            "\n================================================================================="
-        }
-        log { "STATE $reason" }
-        log { getStateStr() }
-        log {
-            "=================================================================================\n"
-        }
+        return "\navalanche state:" +
+                "\n\tshowing: [${getKey(headsUpEntryShowing)}]" +
+                "\n\tprevious: [$previousHunKey]" +
+                "\n\tnext list: $nextListStr" +
+                "\n\tnext map: $nextMapStr" +
+                "\n\tdropped: $dropSetStr"
     }
 
     private val dropSetStr: String
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index 6ffb162..80c595f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -66,6 +66,30 @@
         })
     }
 
+    fun logAvalancheUpdate(caller: String, isEnabled: Boolean, notifEntryKey: String,
+                           outcome: String) {
+        buffer.log(TAG, INFO, {
+            str1 = caller
+            str2 = notifEntryKey
+            str3 = outcome
+            bool1 = isEnabled
+        }, {
+            "$str1\n\t=> AC[isEnabled:$bool1] update: $str2\n\t=> $str3"
+        })
+    }
+
+    fun logAvalancheDelete(caller: String, isEnabled: Boolean, notifEntryKey: String,
+                           outcome: String) {
+        buffer.log(TAG, INFO, {
+            str1 = caller
+            str2 = notifEntryKey
+            str3 = outcome
+            bool1 = isEnabled
+        }, {
+            "$str1\n\t=> AC[isEnabled:$bool1] delete: $str2\n\t=> $str3"
+        })
+    }
+
     fun logShowNotification(entry: NotificationEntry) {
         buffer.log(TAG, INFO, {
             str1 = entry.logKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
index 8aa989f..2b094d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
@@ -18,155 +18,67 @@
 
 import android.content.Intent
 import android.provider.Settings
-import android.util.Log
 import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
 import androidx.compose.ui.res.stringResource
-import androidx.lifecycle.DefaultLifecycleObserver
-import androidx.lifecycle.LifecycleOwner
 import com.android.compose.PlatformButton
 import com.android.compose.PlatformOutlinedButton
-import com.android.internal.annotations.VisibleForTesting
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.animation.Expandable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dialog.ui.composable.AlertDialogContent
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
-import com.android.systemui.statusbar.phone.ComponentSystemUIDialog
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import com.android.systemui.statusbar.phone.create
 import com.android.systemui.statusbar.policy.ui.dialog.composable.ModeTileGrid
 import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel
-import com.android.systemui.util.Assert
 import javax.inject.Inject
-import javax.inject.Provider
-import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.withContext
 
-@SysUISingleton
 class ModesDialogDelegate
 @Inject
 constructor(
     private val sysuiDialogFactory: SystemUIDialogFactory,
     private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val activityStarter: ActivityStarter,
-    // Using a provider to avoid a circular dependency.
-    private val viewModel: Provider<ModesDialogViewModel>,
-    @Main private val mainCoroutineContext: CoroutineContext,
+    private val viewModel: ModesDialogViewModel,
 ) : SystemUIDialog.Delegate {
-    // NOTE: This should only be accessed/written from the main thread.
-    @VisibleForTesting var currentDialog: ComponentSystemUIDialog? = null
-
     override fun createDialog(): SystemUIDialog {
-        Assert.isMainThread()
-        if (currentDialog != null) {
-            Log.w(TAG, "Dialog is already open, dismissing it and creating a new one.")
-            currentDialog?.dismiss()
-        }
-
-        currentDialog = sysuiDialogFactory.create() { ModesDialogContent(it) }
-        currentDialog
-            ?.lifecycle
-            ?.addObserver(
-                object : DefaultLifecycleObserver {
-                    override fun onStop(owner: LifecycleOwner) {
-                        Assert.isMainThread()
-                        currentDialog = null
+        return sysuiDialogFactory.create { dialog ->
+            AlertDialogContent(
+                title = { Text(stringResource(R.string.zen_modes_dialog_title)) },
+                content = { ModeTileGrid(viewModel) },
+                neutralButton = {
+                    PlatformOutlinedButton(
+                        onClick = {
+                            val animationController =
+                                dialogTransitionAnimator.createActivityTransitionController(
+                                    dialog.getButton(SystemUIDialog.BUTTON_NEUTRAL)
+                                )
+                            if (animationController == null) {
+                                // The controller will take care of dismissing for us after the
+                                // animation, but let's make sure we dismiss the dialog if we don't
+                                // animate it.
+                                dialog.dismiss()
+                            }
+                            activityStarter.startActivity(
+                                ZEN_MODE_SETTINGS_INTENT,
+                                true /* dismissShade */,
+                                animationController
+                            )
+                        }
+                    ) {
+                        Text(stringResource(R.string.zen_modes_dialog_settings))
                     }
-                }
-            )
-
-        return currentDialog!!
-    }
-
-    @Composable
-    private fun ModesDialogContent(dialog: SystemUIDialog) {
-        AlertDialogContent(
-            title = { Text(stringResource(R.string.zen_modes_dialog_title)) },
-            content = { ModeTileGrid(viewModel.get()) },
-            neutralButton = {
-                PlatformOutlinedButton(onClick = { openSettings(dialog) }) {
-                    Text(stringResource(R.string.zen_modes_dialog_settings))
-                }
-            },
-            positiveButton = {
-                PlatformButton(onClick = { dialog.dismiss() }) {
-                    Text(stringResource(R.string.zen_modes_dialog_done))
-                }
-            },
-        )
-    }
-
-    private fun openSettings(dialog: SystemUIDialog) {
-        val animationController =
-            dialogTransitionAnimator.createActivityTransitionController(dialog)
-        if (animationController == null) {
-            // The controller will take care of dismissing for us after
-            // the animation, but let's make sure we dismiss the dialog
-            // if we don't animate it.
-            dialog.dismiss()
-        }
-        activityStarter.startActivity(
-            ZEN_MODE_SETTINGS_INTENT,
-            true /* dismissShade */,
-            animationController
-        )
-    }
-
-    suspend fun showDialog(expandable: Expandable? = null): SystemUIDialog {
-        // Dialogs shown by the DialogTransitionAnimator must be created and shown on the main
-        // thread, so we post it to the UI handler.
-        withContext(mainCoroutineContext) {
-            // Create the dialog if necessary
-            if (currentDialog == null) {
-                createDialog()
-            }
-
-            expandable
-                ?.dialogTransitionController(
-                    DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
-                )
-                ?.let { controller -> dialogTransitionAnimator.show(currentDialog!!, controller) }
-                ?: currentDialog!!.show()
-        }
-
-        return currentDialog!!
-    }
-
-    /**
-     * Launches the [intent] by animating from the dialog. If the dialog is not showing, just
-     * launches it normally without animating.
-     */
-    fun launchFromDialog(intent: Intent) {
-        Assert.isMainThread()
-        if (currentDialog == null) {
-            Log.w(
-                TAG,
-                "Cannot launch from dialog, the dialog is not present. " +
-                    "Will launch activity without animating."
+                },
+                positiveButton = {
+                    PlatformButton(onClick = { dialog.dismiss() }) {
+                        Text(stringResource(R.string.zen_modes_dialog_done))
+                    }
+                },
             )
         }
-
-        val animationController =
-            currentDialog?.let { dialogTransitionAnimator.createActivityTransitionController(it) }
-        if (animationController == null) {
-            currentDialog?.dismiss()
-        }
-        activityStarter.startActivity(
-            intent,
-            true, /* dismissShade */
-            animationController,
-        )
     }
 
     companion object {
-        private const val TAG = "ModesDialogDelegate"
         private val ZEN_MODE_SETTINGS_INTENT = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
-        private const val INTERACTION_JANK_TAG = "configure_priority_modes"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index 7e64090..e84c8b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -17,15 +17,11 @@
 package com.android.systemui.statusbar.policy.ui.dialog.viewmodel
 
 import android.content.Context
-import android.content.Intent
-import android.provider.Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS
-import android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID
 import com.android.settingslib.notification.modes.ZenMode
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
-import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
@@ -43,7 +39,6 @@
     val context: Context,
     zenModeInteractor: ZenModeInteractor,
     @Background val bgDispatcher: CoroutineDispatcher,
-    private val dialogDelegate: ModesDialogDelegate,
 ) {
     // Modes that should be displayed in the dialog
     // TODO(b/346519570): Include modes that have not been set up yet.
@@ -76,10 +71,7 @@
                             }
                         },
                         onLongClick = {
-                            val intent: Intent =
-                                Intent(ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
-                                    .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, mode.id)
-                            dialogDelegate.launchFromDialog(intent)
+                            // TODO(b/346519570): Open settings page for mode.
                         }
                     )
                 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index f726aae..e251ab5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argThat
 import com.android.systemui.util.mockito.whenever
+import java.util.function.Predicate
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
@@ -46,7 +47,6 @@
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoAnnotations
 import org.mockito.kotlin.clearInvocations
-import java.util.function.Predicate
 
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper
@@ -54,70 +54,134 @@
 class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
     private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
 
-    @Mock
-    private lateinit var windowManager: WindowManager
-    @Mock
-    private lateinit var keyguardViewMediator: KeyguardViewMediator
-    @Mock
-    private lateinit var keyguardStateController: KeyguardStateController
-    @Mock
-    private lateinit var keyguardViewController: KeyguardViewController
-    @Mock
-    private lateinit var featureFlags: FeatureFlags
-    @Mock
-    private lateinit var biometricUnlockController: BiometricUnlockController
-    @Mock
-    private lateinit var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
-    @Mock
-    private lateinit var statusBarStateController: SysuiStatusBarStateController
-    @Mock
-    private lateinit var notificationShadeWindowController: NotificationShadeWindowController
-    @Mock
-    private lateinit var powerManager: PowerManager
-    @Mock
-    private lateinit var wallpaperManager: WallpaperManager
+    @Mock private lateinit var windowManager: WindowManager
+    @Mock private lateinit var keyguardViewMediator: KeyguardViewMediator
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var keyguardViewController: KeyguardViewController
+    @Mock private lateinit var featureFlags: FeatureFlags
+    @Mock private lateinit var biometricUnlockController: BiometricUnlockController
+    @Mock private lateinit var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
+    @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+    @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+    @Mock private lateinit var powerManager: PowerManager
+    @Mock private lateinit var wallpaperManager: WallpaperManager
 
     @Mock
     private lateinit var launcherUnlockAnimationController: ILauncherUnlockAnimationController.Stub
 
     private var surfaceControl1 = mock(SurfaceControl::class.java)
-    private var remoteTarget1 = RemoteAnimationTarget(
-            0 /* taskId */, 0, surfaceControl1, false, Rect(), Rect(), 0, Point(), Rect(), Rect(),
-            mock(WindowConfiguration::class.java), false, surfaceControl1, Rect(),
-            mock(ActivityManager.RunningTaskInfo::class.java), false)
+    private var remoteTarget1 =
+        RemoteAnimationTarget(
+            0 /* taskId */,
+            0,
+            surfaceControl1,
+            false,
+            Rect(),
+            Rect(),
+            0,
+            Point(),
+            Rect(),
+            Rect(),
+            mock(WindowConfiguration::class.java),
+            false,
+            surfaceControl1,
+            Rect(),
+            mock(ActivityManager.RunningTaskInfo::class.java),
+            false
+        )
 
     private var surfaceControl2 = mock(SurfaceControl::class.java)
-    private var remoteTarget2 = RemoteAnimationTarget(
-            1 /* taskId */, 0, surfaceControl2, false, Rect(), Rect(), 0, Point(), Rect(), Rect(),
-            mock(WindowConfiguration::class.java), false, surfaceControl2, Rect(),
-            mock(ActivityManager.RunningTaskInfo::class.java), false)
+    private var remoteTarget2 =
+        RemoteAnimationTarget(
+            1 /* taskId */,
+            0,
+            surfaceControl2,
+            false,
+            Rect(),
+            Rect(),
+            0,
+            Point(),
+            Rect(),
+            Rect(),
+            mock(WindowConfiguration::class.java),
+            false,
+            surfaceControl2,
+            Rect(),
+            mock(ActivityManager.RunningTaskInfo::class.java),
+            false
+        )
     private lateinit var remoteAnimationTargets: Array<RemoteAnimationTarget>
 
     private var surfaceControlWp = mock(SurfaceControl::class.java)
-    private var wallpaperTarget = RemoteAnimationTarget(
-            2 /* taskId */, 0, surfaceControlWp, false, Rect(), Rect(), 0, Point(), Rect(), Rect(),
-            mock(WindowConfiguration::class.java), false, surfaceControlWp, Rect(),
-            mock(ActivityManager.RunningTaskInfo::class.java), false)
+    private var wallpaperTarget =
+        RemoteAnimationTarget(
+            2 /* taskId */,
+            0,
+            surfaceControlWp,
+            false,
+            Rect(),
+            Rect(),
+            0,
+            Point(),
+            Rect(),
+            Rect(),
+            mock(WindowConfiguration::class.java),
+            false,
+            surfaceControlWp,
+            Rect(),
+            mock(ActivityManager.RunningTaskInfo::class.java),
+            false
+        )
     private lateinit var wallpaperTargets: Array<RemoteAnimationTarget>
 
     private var surfaceControlLockWp = mock(SurfaceControl::class.java)
-    private var lockWallpaperTarget = RemoteAnimationTarget(
-            3 /* taskId */, 0, surfaceControlLockWp, false, Rect(), Rect(), 0, Point(), Rect(),
-            Rect(), mock(WindowConfiguration::class.java), false, surfaceControlLockWp,
-            Rect(), mock(ActivityManager.RunningTaskInfo::class.java), false)
+    private var lockWallpaperTarget =
+        RemoteAnimationTarget(
+            3 /* taskId */,
+            0,
+            surfaceControlLockWp,
+            false,
+            Rect(),
+            Rect(),
+            0,
+            Point(),
+            Rect(),
+            Rect(),
+            mock(WindowConfiguration::class.java),
+            false,
+            surfaceControlLockWp,
+            Rect(),
+            mock(ActivityManager.RunningTaskInfo::class.java),
+            false
+        )
     private lateinit var lockWallpaperTargets: Array<RemoteAnimationTarget>
+    private var shouldPerformSmartspaceTransition = false
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        keyguardUnlockAnimationController = KeyguardUnlockAnimationController(
-            windowManager, context.resources,
-            keyguardStateController, { keyguardViewMediator }, keyguardViewController,
-            featureFlags, { biometricUnlockController }, statusBarStateController,
-            notificationShadeWindowController, powerManager, wallpaperManager
-        )
+        keyguardUnlockAnimationController =
+            object :
+                KeyguardUnlockAnimationController(
+                    windowManager,
+                    context.resources,
+                    keyguardStateController,
+                    { keyguardViewMediator },
+                    keyguardViewController,
+                    featureFlags,
+                    { biometricUnlockController },
+                    statusBarStateController,
+                    notificationShadeWindowController,
+                    powerManager,
+                    wallpaperManager
+                ) {
+                override fun shouldPerformSmartspaceTransition(): Boolean =
+                    shouldPerformSmartspaceTransition
+            }
         keyguardUnlockAnimationController.setLauncherUnlockController(
-            "", launcherUnlockAnimationController)
+            "",
+            launcherUnlockAnimationController
+        )
 
         whenever(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java))
         whenever(powerManager.isInteractive).thenReturn(true)
@@ -159,8 +223,8 @@
         )
 
         val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
-        verify(surfaceTransactionApplier, times(1)).scheduleApply(
-                captorSb.capture { sp -> sp.surface == surfaceControl1 })
+        verify(surfaceTransactionApplier, times(1))
+            .scheduleApply(captorSb.capture { sp -> sp.surface == surfaceControl1 })
 
         val params = captorSb.getLastValue()
 
@@ -171,15 +235,13 @@
 
         // Also expect we've immediately asked the keyguard view mediator to finish the remote
         // animation.
-        verify(keyguardViewMediator, times(1)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
-            false /* cancelled */)
+        verify(keyguardViewMediator, times(1))
+            .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
 
         verifyNoMoreInteractions(surfaceTransactionApplier)
     }
 
-    /**
-     * If we are not wake and unlocking, we expect the unlock animation to play normally.
-     */
+    /** If we are not wake and unlocking, we expect the unlock animation to play normally. */
     @Test
     fun surfaceAnimation_ifNotWakeAndUnlocking() {
         whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(false)
@@ -193,18 +255,18 @@
         )
 
         // Since the animation is running, we should not have finished the remote animation.
-        verify(keyguardViewMediator, times(0)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
-            false /* cancelled */)
+        verify(keyguardViewMediator, times(0))
+            .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
     }
 
     @Test
     fun onWakeAndUnlock_notifiesListenerWithTrue() {
         whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
-        whenever(biometricUnlockController.mode).thenReturn(
-            BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
+        whenever(biometricUnlockController.mode)
+            .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
 
-        val listener = mock(
-            KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
+        val listener =
+            mock(KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
         keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(listener)
 
         keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
@@ -221,11 +283,11 @@
     @Test
     fun onWakeAndUnlockFromDream_notifiesListenerWithFalse() {
         whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
-        whenever(biometricUnlockController.mode).thenReturn(
-            BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+        whenever(biometricUnlockController.mode)
+            .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
 
-        val listener = mock(
-            KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
+        val listener =
+            mock(KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
         keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(listener)
 
         keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
@@ -269,8 +331,8 @@
      * keyguard. This means this was a swipe to dismiss gesture but the user flung the keyguard and
      * lifted their finger while we were requesting the surface be made visible.
      *
-     * In this case, we should verify that we are playing the canned unlock animation and not
-     * simply fading in the surface.
+     * In this case, we should verify that we are playing the canned unlock animation and not simply
+     * fading in the surface.
      */
     @Test
     fun playCannedUnlockAnimation_ifRequestedShowSurface_andFlinging() {
@@ -293,8 +355,8 @@
      * ever happened and we're just playing the simple canned animation (happens via UDFPS unlock,
      * long press on the lock icon, etc).
      *
-     * In this case, we should verify that we are playing the canned unlock animation and not
-     * simply fading in the surface.
+     * In this case, we should verify that we are playing the canned unlock animation and not simply
+     * fading in the surface.
      */
     @Test
     fun playCannedUnlockAnimation_ifDidNotRequestShowSurface() {
@@ -332,11 +394,11 @@
         keyguardUnlockAnimationController.willUnlockWithInWindowLauncherAnimations = true
 
         keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
-                remoteAnimationTargets,
-                wallpaperTargets,
-                arrayOf(),
-                0 /* startTime */,
-                false /* requestedShowSurfaceBehindKeyguard */
+            remoteAnimationTargets,
+            wallpaperTargets,
+            arrayOf(),
+            0 /* startTime */,
+            false /* requestedShowSurfaceBehindKeyguard */
         )
 
         assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation())
@@ -353,11 +415,11 @@
         var lastFadeOutAlpha = -1f
 
         keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
-                arrayOf(remoteTarget1, remoteTarget2),
-                wallpaperTargets,
-                lockWallpaperTargets,
-                0 /* startTime */,
-                false /* requestedShowSurfaceBehindKeyguard */
+            arrayOf(remoteTarget1, remoteTarget2),
+            wallpaperTargets,
+            lockWallpaperTargets,
+            0 /* startTime */,
+            false /* requestedShowSurfaceBehindKeyguard */
         )
 
         for (i in 0..10) {
@@ -367,19 +429,22 @@
             keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(amount)
 
             val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
-            verify(surfaceTransactionApplier, times(2)).scheduleApply(
+            verify(surfaceTransactionApplier, times(2))
+                .scheduleApply(
                     captorSb.capture { sp ->
-                        sp.surface == surfaceControlWp || sp.surface == surfaceControlLockWp })
+                        sp.surface == surfaceControlWp || sp.surface == surfaceControlLockWp
+                    }
+                )
 
             val fadeInAlpha = captorSb.getLastValue { it.surface == surfaceControlWp }.alpha
             val fadeOutAlpha = captorSb.getLastValue { it.surface == surfaceControlLockWp }.alpha
 
             if (amount == 0f) {
-                assertTrue (fadeInAlpha == 0f)
-                assertTrue (fadeOutAlpha == 1f)
+                assertTrue(fadeInAlpha == 0f)
+                assertTrue(fadeOutAlpha == 1f)
             } else if (amount == 1f) {
-                assertTrue (fadeInAlpha == 1f)
-                assertTrue (fadeOutAlpha == 0f)
+                assertTrue(fadeInAlpha == 1f)
+                assertTrue(fadeOutAlpha == 0f)
             } else {
                 assertTrue(fadeInAlpha >= lastFadeInAlpha)
                 assertTrue(fadeOutAlpha <= lastFadeOutAlpha)
@@ -389,18 +454,16 @@
         }
     }
 
-    /**
-     * If we are not wake and unlocking, we expect the unlock animation to play normally.
-     */
+    /** If we are not wake and unlocking, we expect the unlock animation to play normally. */
     @Test
     @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun surfaceAnimation_multipleTargets() {
         keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
-                arrayOf(remoteTarget1, remoteTarget2),
-                wallpaperTargets,
-                arrayOf(),
-                0 /* startTime */,
-                false /* requestedShowSurfaceBehindKeyguard */
+            arrayOf(remoteTarget1, remoteTarget2),
+            wallpaperTargets,
+            arrayOf(),
+            0 /* startTime */,
+            false /* requestedShowSurfaceBehindKeyguard */
         )
 
         // Cancel the animator so we can verify only the setSurfaceBehind call below.
@@ -412,12 +475,18 @@
         keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(0.5f)
 
         val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
-        verify(surfaceTransactionApplier, times(2)).scheduleApply(captorSb
-                .capture { sp -> sp.surface == surfaceControl1 || sp.surface == surfaceControl2 })
+        verify(surfaceTransactionApplier, times(2))
+            .scheduleApply(
+                captorSb.capture { sp ->
+                    sp.surface == surfaceControl1 || sp.surface == surfaceControl2
+                }
+            )
         val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
-        verify(surfaceTransactionApplier, times(1).description(
-                "WallpaperSurface was expected to receive scheduleApply once"
-        )).scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp})
+        verify(
+                surfaceTransactionApplier,
+                times(1).description("WallpaperSurface was expected to receive scheduleApply once")
+            )
+            .scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp })
 
         val allParams = captorSb.getAllValues()
 
@@ -432,8 +501,8 @@
         assertTrue(remainingTargets.isEmpty())
 
         // Since the animation is running, we should not have finished the remote animation.
-        verify(keyguardViewMediator, times(0)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
-                false /* cancelled */)
+        verify(keyguardViewMediator, times(0))
+            .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
     }
 
     @Test
@@ -442,11 +511,11 @@
         whenever(powerManager.isInteractive).thenReturn(false)
 
         keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
-                remoteAnimationTargets,
-                wallpaperTargets,
-                arrayOf(),
-                0 /* startTime */,
-                false /* requestedShowSurfaceBehindKeyguard */
+            remoteAnimationTargets,
+            wallpaperTargets,
+            arrayOf(),
+            0 /* startTime */,
+            false /* requestedShowSurfaceBehindKeyguard */
         )
 
         // Cancel the animator so we can verify only the setSurfaceBehind call below.
@@ -457,12 +526,14 @@
         keyguardUnlockAnimationController.setWallpaperAppearAmount(1f, wallpaperTargets)
 
         val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
-        verify(surfaceTransactionApplier, times(1)).scheduleApply(
-                captorSb.capture { sp -> sp.surface == surfaceControl1})
+        verify(surfaceTransactionApplier, times(1))
+            .scheduleApply(captorSb.capture { sp -> sp.surface == surfaceControl1 })
         val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
-        verify(surfaceTransactionApplier, atLeastOnce().description("Wallpaper surface has  not " +
-                "received scheduleApply")).scheduleApply(
-                captorWp.capture { sp -> sp.surface == surfaceControlWp })
+        verify(
+                surfaceTransactionApplier,
+                atLeastOnce().description("Wallpaper surface has  not " + "received scheduleApply")
+            )
+            .scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp })
 
         val params = captorSb.getLastValue()
 
@@ -479,11 +550,11 @@
         whenever(powerManager.isInteractive).thenReturn(true)
 
         keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
-                remoteAnimationTargets,
-                wallpaperTargets,
-                arrayOf(),
-                0 /* startTime */,
-                false /* requestedShowSurfaceBehindKeyguard */
+            remoteAnimationTargets,
+            wallpaperTargets,
+            arrayOf(),
+            0 /* startTime */,
+            false /* requestedShowSurfaceBehindKeyguard */
         )
 
         // Stop the animator - we just want to test whether the override is not applied.
@@ -494,24 +565,31 @@
         keyguardUnlockAnimationController.setWallpaperAppearAmount(1f, wallpaperTargets)
 
         val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
-        verify(surfaceTransactionApplier, times(1)).scheduleApply(
-                captorSb.capture { sp -> sp.surface == surfaceControl1 })
+        verify(surfaceTransactionApplier, times(1))
+            .scheduleApply(captorSb.capture { sp -> sp.surface == surfaceControl1 })
         val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
-        verify(surfaceTransactionApplier, atLeastOnce().description("Wallpaper surface has  not " +
-                "received scheduleApply")).scheduleApply(
-                captorWp.capture { sp -> sp.surface == surfaceControlWp })
+        verify(
+                surfaceTransactionApplier,
+                atLeastOnce().description("Wallpaper surface has  not " + "received scheduleApply")
+            )
+            .scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp })
 
         val params = captorSb.getLastValue()
         assertEquals(1f, params.alpha)
         assertTrue(params.matrix.isIdentity)
-        assertEquals("Wallpaper surface was expected to have opacity 1",
-                1f, captorWp.getLastValue().alpha)
+        assertEquals(
+            "Wallpaper surface was expected to have opacity 1",
+            1f,
+            captorWp.getLastValue().alpha
+        )
 
         verifyNoMoreInteractions(surfaceTransactionApplier)
     }
 
     @Test
-    fun unlockToLauncherWithInWindowAnimations_ssViewIsVisible() {
+    fun unlockToLauncherWithInWindowAnimations_ssViewInVisible_whenPerformSSTransition() {
+        shouldPerformSmartspaceTransition = true
+
         val mockLockscreenSmartspaceView = mock(View::class.java)
         whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.VISIBLE)
         keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView
@@ -522,6 +600,19 @@
     }
 
     @Test
+    fun unlockToLauncherWithInWindowAnimations_ssViewVisible_whenNotPerformSSTransition() {
+        shouldPerformSmartspaceTransition = false
+
+        val mockLockscreenSmartspaceView = mock(View::class.java)
+        whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.VISIBLE)
+        keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView
+
+        keyguardUnlockAnimationController.unlockToLauncherWithInWindowAnimations()
+
+        verify(mockLockscreenSmartspaceView, never()).visibility = View.INVISIBLE
+    }
+
+    @Test
     fun unlockToLauncherWithInWindowAnimations_ssViewIsInvisible() {
         val mockLockscreenSmartspaceView = mock(View::class.java)
         whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.INVISIBLE)
@@ -591,7 +682,7 @@
         private var allArgs: MutableList<T> = mutableListOf()
 
         fun capture(predicate: Predicate<T>): T {
-            return argThat{x: T ->
+            return argThat { x: T ->
                 if (predicate.test(x)) {
                     allArgs.add(x)
                     return@argThat true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
index 7dd8028..f884b87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
@@ -25,8 +25,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
@@ -34,10 +32,8 @@
 import com.android.systemui.util.mockito.mock
 import junit.framework.Assert.assertEquals
 import org.junit.After
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -46,7 +42,6 @@
 
     private lateinit var dialog: AlertDialog
 
-    private val flags = mock<FeatureFlagsClassic>()
     private val appName = "Test App"
 
     private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
@@ -54,11 +49,6 @@
     private val resIdSingleAppDisabled =
         R.string.media_projection_entry_app_permission_dialog_single_app_disabled
 
-    @Before
-    fun setUp() {
-        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
-    }
-
     @After
     fun teardown() {
         if (::dialog.isInitialized) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
index 74d9692..27b6ea6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
@@ -27,6 +27,7 @@
 import com.android.internal.logging.MetricsLogger
 import com.android.settingslib.notification.data.repository.FakeZenModeRepository
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -46,6 +47,7 @@
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
+import kotlin.coroutines.EmptyCoroutineContext
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
@@ -80,6 +82,8 @@
 
     @Mock private lateinit var qsTileConfigProvider: QSTileConfigProvider
 
+    @Mock private lateinit var dialogTransitionAnimator: DialogTransitionAnimator
+
     @Mock private lateinit var dialogDelegate: ModesDialogDelegate
 
     private val inputHandler = FakeQSTileIntentUserInputHandler()
@@ -127,7 +131,9 @@
 
         userActionInteractor =
             ModesTileUserActionInteractor(
+                EmptyCoroutineContext,
                 inputHandler,
+                dialogTransitionAnimator,
                 dialogDelegate,
             )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index ce1a885..8d84c3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -177,7 +177,6 @@
             .thenReturn(false)
         whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
             .thenReturn(false)
-        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
         whenever(state.hasUserApprovedScreenRecording).thenReturn(false)
 
         val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
@@ -200,7 +199,6 @@
             .thenReturn(false)
         whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
             .thenReturn(false)
-        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(false)
 
         val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
         screenRecordSwitch.isChecked = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 477c50b..6b16e78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -39,10 +39,8 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
-import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogTransitionAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -52,12 +50,9 @@
 import com.android.systemui.mediaprojection.SessionCreationSource;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate;
-import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.DialogDelegate;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -110,7 +105,6 @@
 
     private FakeFeatureFlags mFeatureFlags;
     private RecordingController mController;
-    private TestSystemUIDialogFactory mDialogFactory;
 
     private static final int USER_ID = 10;
 
@@ -120,14 +114,6 @@
         Context spiedContext = spy(mContext);
         when(spiedContext.getUserId()).thenReturn(TEST_USER_ID);
 
-        mDialogFactory = new TestSystemUIDialogFactory(
-                mContext,
-                Dependency.get(SystemUIDialogManager.class),
-                Dependency.get(SysUiState.class),
-                Dependency.get(BroadcastDispatcher.class),
-                Dependency.get(DialogTransitionAnimator.class)
-        );
-
         mFeatureFlags = new FakeFeatureFlags();
         when(mScreenCaptureDisabledDialogDelegate.createSysUIDialog())
                 .thenReturn(mScreenCaptureDisabledDialog);
@@ -251,7 +237,6 @@
 
     @Test
     public void testPoliciesFlagDisabled_screenCapturingNotAllowed_returnsNullDevicePolicyDialog() {
-        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
         mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
         when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
 
@@ -269,19 +254,7 @@
     }
 
     @Test
-    public void testPartialScreenSharingDisabled_returnsLegacyDialog() {
-        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, false);
-        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
-
-        Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
-                mDialogTransitionAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
-
-        assertThat(dialog).isEqualTo(mScreenRecordSystemUIDialog);
-    }
-
-    @Test
     public void testPoliciesFlagEnabled_screenCapturingNotAllowed_returnsDevicePolicyDialog() {
-        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
         mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
         when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
 
@@ -293,7 +266,6 @@
 
     @Test
     public void testPoliciesFlagEnabled_screenCapturingAllowed_returnsNullDevicePolicyDialog() {
-        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
         mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
         when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
 
@@ -312,7 +284,6 @@
 
     @Test
     public void testPoliciesFlagEnabled_screenCapturingAllowed_logsProjectionInitiated() {
-        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
         mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
         when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
 
@@ -324,32 +295,4 @@
                         /* hostUid= */ myUid(),
                         SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
     }
-
-    private static class TestSystemUIDialogFactory extends SystemUIDialog.Factory {
-
-        @Nullable private DialogDelegate<SystemUIDialog> mLastDelegate;
-        @Nullable private SystemUIDialog mLastCreatedDialog;
-
-        TestSystemUIDialogFactory(
-                Context context,
-                SystemUIDialogManager systemUIDialogManager,
-                SysUiState sysUiState,
-                BroadcastDispatcher broadcastDispatcher,
-                DialogTransitionAnimator dialogTransitionAnimator) {
-            super(
-                    context,
-                    systemUIDialogManager,
-                    sysUiState,
-                    broadcastDispatcher,
-                    dialogTransitionAnimator);
-        }
-
-        @Override
-        public SystemUIDialog create(SystemUIDialog.Delegate delegate) {
-            SystemUIDialog dialog = super.create(delegate);
-            mLastDelegate = delegate;
-            mLastCreatedDialog = dialog;
-            return dialog;
-        }
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index cc8d7d5..11b0bdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -28,7 +28,6 @@
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity
 import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
@@ -51,7 +50,6 @@
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -72,8 +70,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
-
         val systemUIDialogFactory =
             SystemUIDialog.Factory(
                 context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
deleted file mode 100644
index 3908529..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- *
- */
-
-package com.android.systemui.statusbar.notification.domain.interactor
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-class SeenNotificationsInteractorTest : SysuiTestCase() {
-
-    private val repository = ActiveNotificationListRepository()
-    private val underTest = SeenNotificationsInteractor(repository)
-
-    @Test
-    fun testNoFilteredOutSeenNotifications() = runTest {
-        val hasFilteredOutSeenNotifications by
-            collectLastValue(underTest.hasFilteredOutSeenNotifications)
-
-        underTest.setHasFilteredOutSeenNotifications(false)
-
-        assertThat(hasFilteredOutSeenNotifications).isFalse()
-    }
-
-    @Test
-    fun testHasFilteredOutSeenNotifications() = runTest {
-        val hasFilteredOutSeenNotifications by
-            collectLastValue(underTest.hasFilteredOutSeenNotifications)
-
-        underTest.setHasFilteredOutSeenNotifications(true)
-
-        assertThat(hasFilteredOutSeenNotifications).isTrue()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 1060b62..c36a046 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -91,7 +91,6 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifStats;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
@@ -188,12 +187,8 @@
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
 
-
-    private final ActiveNotificationListRepository mActiveNotificationsRepository =
-            new ActiveNotificationListRepository();
-
     private final SeenNotificationsInteractor mSeenNotificationsInteractor =
-            new SeenNotificationsInteractor(mActiveNotificationsRepository);
+            mKosmos.getSeenNotificationsInteractor();
 
     private NotificationStackScrollLayoutController mController;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index fd2dead..e670884 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -334,6 +334,7 @@
             /* typeVisibilityMap = */ booleanArrayOf(),
             /* isRound = */ false,
             /* forceConsumingTypes = */ 0,
+            /* forceConsumingCaptionBar = */ false,
             /* suppressScrimTypes = */ 0,
             /* displayCutout = */ DisplayCutout.NO_CUTOUT,
             /* roundedCorners = */ RoundedCorners.NO_ROUNDED_CORNERS,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 31f93b4..af5e60e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
@@ -94,6 +95,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.model.Scenes;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
@@ -166,6 +168,7 @@
     @Mock private StatusBarKeyguardViewManager.KeyguardViewManagerCallback mCallback;
     @Mock private SelectedUserInteractor mSelectedUserInteractor;
     @Mock private DeviceEntryInteractor mDeviceEntryInteractor;
+    @Mock private SceneInteractor mSceneInteractor;
 
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
@@ -233,7 +236,7 @@
                         mSelectedUserInteractor,
                         () -> mock(KeyguardSurfaceBehindInteractor.class),
                         mock(JavaAdapter.class),
-                        () -> mock(SceneInteractor.class),
+                        () -> mSceneInteractor,
                         mock(StatusBarKeyguardViewManagerInteractor.class),
                         () -> mDeviceEntryInteractor) {
                     @Override
@@ -270,21 +273,23 @@
     }
 
     @Test
-    public void showBouncer_onlyWhenShowing() {
+    public void showPrimaryBouncer_onlyWhenShowing() {
         mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
         mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
         verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
         verify(mDeviceEntryInteractor, never()).attemptDeviceEntry();
+        verify(mSceneInteractor, never()).changeScene(any(), any());
     }
 
     @Test
-    public void showBouncer_notWhenBouncerAlreadyShowing() {
+    public void showPrimaryBouncer_notWhenBouncerAlreadyShowing() {
         mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
         when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
                 KeyguardSecurityModel.SecurityMode.Password);
         mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
         verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
         verify(mDeviceEntryInteractor, never()).attemptDeviceEntry();
+        verify(mSceneInteractor, never()).changeScene(any(), any());
     }
 
     @Test
@@ -753,7 +758,7 @@
                         mSelectedUserInteractor,
                         () -> mock(KeyguardSurfaceBehindInteractor.class),
                         mock(JavaAdapter.class),
-                        () -> mock(SceneInteractor.class),
+                        () -> mSceneInteractor,
                         mock(StatusBarKeyguardViewManagerInteractor.class),
                         () -> mDeviceEntryInteractor) {
                     @Override
@@ -1104,9 +1109,9 @@
 
     @Test
     @EnableSceneContainer
-    public void showPrimaryBouncer_attemptDeviceEntry() {
+    public void showPrimaryBouncer() {
         mStatusBarKeyguardViewManager.showPrimaryBouncer(false);
-        verify(mDeviceEntryInteractor).attemptDeviceEntry();
+        verify(mSceneInteractor).changeScene(eq(Scenes.Bouncer), anyString());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
new file mode 100644
index 0000000..1f04a44
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.activatable
+
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+
+/** Activates [activatable] for the duration of the test. */
+fun Activatable.activateIn(testScope: TestScope) {
+    testScope.backgroundScope.launch { activate() }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
index 5ac41ec..b23767e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
@@ -18,10 +18,6 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testCase
-import com.android.systemui.util.mockito.mock
-
-val Kosmos.mockActivityTransitionAnimatorController by
-    Kosmos.Fixture { mock<ActivityTransitionAnimator.Controller>() }
 
 val Kosmos.activityTransitionAnimator by
     Kosmos.Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
index 8e4461d..444baa0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.inputmethod.data.repository
 
+import android.os.UserHandle
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.inputmethod.data.model.InputMethodModel
 import kotlinx.coroutines.flow.Flow
@@ -40,14 +41,15 @@
     }
 
     override suspend fun enabledInputMethods(
-        userId: Int,
-        fetchSubtypes: Boolean,
+        user: UserHandle,
+        fetchSubtypes: Boolean
     ): Flow<InputMethodModel> {
-        return usersToEnabledInputMethods[userId] ?: flowOf()
+        return usersToEnabledInputMethods[user.identifier] ?: flowOf()
     }
 
-    override suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype> =
-        selectedInputMethodSubtypes
+    override suspend fun selectedInputMethodSubtypes(
+        user: UserHandle,
+    ): List<InputMethodModel.Subtype> = selectedInputMethodSubtypes
 
     override suspend fun showInputMethodPicker(displayId: Int, showAuxiliarySubtypes: Boolean) {
         inputMethodPickerShownDisplayId = displayId
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 0666820..8614fc9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -60,6 +60,7 @@
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.shadeController
 import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
+import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
 import com.android.systemui.statusbar.phone.scrimController
@@ -98,6 +99,7 @@
     val communalRepository by lazy { kosmos.fakeCommunalSceneRepository }
     val communalTransitionViewModel by lazy { kosmos.communalTransitionViewModel }
     val headsUpNotificationInteractor by lazy { kosmos.headsUpNotificationInteractor }
+    val seenNotificationsInteractor by lazy { kosmos.seenNotificationsInteractor }
     val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
     val keyguardBouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
     val keyguardInteractor by lazy { kosmos.keyguardInteractor }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
deleted file mode 100644
index 2ecfb45..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.tiles.impl.modes.domain.interactor
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
-import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate
-import javax.inject.Provider
-
-val Kosmos.modesTileUserActionInteractor: ModesTileUserActionInteractor by
-    Kosmos.Fixture {
-        ModesTileUserActionInteractor(
-            qsTileIntentUserInputHandler,
-            Provider { modesDialogDelegate }.get(),
-        )
-    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
index c56c56c..299b22e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.qs.ui.viewmodel
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.ui.viewmodel.overlayShadeViewModel
 
@@ -27,6 +26,5 @@
             shadeInteractor = shadeInteractor,
             overlayShadeViewModel = overlayShadeViewModel,
             quickSettingsContainerViewModel = quickSettingsContainerViewModel,
-            applicationScope = applicationCoroutineScope,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index fff3b14..dd93141 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -2,7 +2,6 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.shared.model.FakeScene
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.Scenes
@@ -18,16 +17,7 @@
     )
 }
 
-val Kosmos.fakeScenes by Fixture {
-    sceneKeys
-        .map { key ->
-            FakeScene(
-                scope = testScope.backgroundScope,
-                key = key,
-            )
-        }
-        .toSet()
-}
+val Kosmos.fakeScenes by Fixture { sceneKeys.map { key -> FakeScene(key) }.toSet() }
 
 val Kosmos.scenes by Fixture { fakeScenes }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
index eeaa9db..64e3526 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
@@ -19,16 +19,12 @@
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.onCompletion
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.receiveAsFlow
-import kotlinx.coroutines.flow.stateIn
 
 class FakeScene(
-    val scope: CoroutineScope,
     override val key: SceneKey,
 ) : Scene {
     var isDestinationScenesBeingCollected = false
@@ -40,11 +36,6 @@
             .receiveAsFlow()
             .onStart { isDestinationScenesBeingCollected = true }
             .onCompletion { isDestinationScenesBeingCollected = false }
-            .stateIn(
-                scope = scope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = emptyMap(),
-            )
 
     suspend fun setDestinationScenes(value: Map<UserAction, UserActionResult>) {
         destinationScenesChannel.send(value)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt
index 989c3a5..2c5a0f4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.shade.ui.viewmodel
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
 import com.android.systemui.qs.footerActionsController
 import com.android.systemui.qs.footerActionsViewModelFactory
@@ -30,7 +29,6 @@
 val Kosmos.shadeSceneViewModel: ShadeSceneViewModel by
     Kosmos.Fixture {
         ShadeSceneViewModel(
-            applicationScope = applicationCoroutineScope,
             shadeHeaderViewModel = shadeHeaderViewModel,
             qsSceneAdapter = qsSceneAdapter,
             brightnessMirrorViewModel = brightnessMirrorViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt
index 77d97bb..933ebf0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt
@@ -18,23 +18,19 @@
 
 import com.android.systemui.dump.dumpManager
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.statusbar.statusBarStateController
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
-import com.android.systemui.util.settings.fakeSettings
 
 var Kosmos.lockScreenMinimalismCoordinator by
     Kosmos.Fixture {
         LockScreenMinimalismCoordinator(
-            bgDispatcher = testDispatcher,
             dumpManager = dumpManager,
             headsUpInteractor = headsUpNotificationInteractor,
             logger = lockScreenMinimalismCoordinatorLogger,
             scope = testScope.backgroundScope,
-            secureSettings = fakeSettings,
             seenNotificationsInteractor = seenNotificationsInteractor,
             statusBarStateController = statusBarStateController,
             shadeInteractor = shadeInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt
index c1e0419..b19e221 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt
@@ -16,12 +16,32 @@
 
 package com.android.systemui.statusbar.notification.domain.interactor
 
+import android.os.UserHandle
+import android.provider.Settings
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.util.settings.fakeSettings
 
 val Kosmos.seenNotificationsInteractor by Fixture {
     SeenNotificationsInteractor(
+        bgDispatcher = testDispatcher,
         notificationListRepository = activeNotificationListRepository,
+        secureSettings = fakeSettings,
     )
 }
+
+var Kosmos.lockScreenShowOnlyUnseenNotificationsSetting: Boolean
+    get() =
+        fakeSettings.getIntForUser(
+            Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+            UserHandle.USER_CURRENT,
+        ) == 1
+    set(value) {
+        fakeSettings.putIntForUser(
+            Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+            if (value) 1 else 2,
+            UserHandle.USER_CURRENT,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
deleted file mode 100644
index 99bb479..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy.ui.dialog
-
-import com.android.systemui.animation.dialogTransitionAnimator
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.mainCoroutineContext
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.statusbar.phone.systemUIDialogFactory
-import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel
-import com.android.systemui.util.mockito.mock
-
-val Kosmos.mockModesDialogDelegate by Kosmos.Fixture { mock<ModesDialogDelegate>() }
-
-var Kosmos.modesDialogDelegate: ModesDialogDelegate by
-    Kosmos.Fixture {
-        ModesDialogDelegate(
-            systemUIDialogFactory,
-            dialogTransitionAnimator,
-            activityStarter,
-            { modesDialogViewModel },
-            mainCoroutineContext,
-        )
-    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt
deleted file mode 100644
index 00020f8..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy.ui.dialog.viewmodel
-
-import android.content.mockedContext
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
-import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate
-import javax.inject.Provider
-
-val Kosmos.modesDialogViewModel: ModesDialogViewModel by
-    Kosmos.Fixture {
-        ModesDialogViewModel(
-            mockedContext,
-            zenModeInteractor,
-            testDispatcher,
-            Provider { modesDialogDelegate }.get(),
-        )
-    }
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index f821e00..69478bb 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4616,6 +4616,9 @@
                     opPackageName,
                     visibleAccountTypes,
                     includeUserManagedNotVisible);
+        } catch (SQLiteException e) {
+            Log.w(TAG, "Could not get accounts for user " + userId, e);
+            return new Account[]{};
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -4703,12 +4706,17 @@
 
     public Account[] getSharedAccountsAsUser(int userId) {
         userId = handleIncomingUser(userId);
-        UserAccounts accounts = getUserAccounts(userId);
-        synchronized (accounts.dbLock) {
-            List<Account> accountList = accounts.accountsDb.getSharedAccounts();
-            Account[] accountArray = new Account[accountList.size()];
-            accountList.toArray(accountArray);
-            return accountArray;
+        try {
+            UserAccounts accounts = getUserAccounts(userId);
+            synchronized (accounts.dbLock) {
+                List<Account> accountList = accounts.accountsDb.getSharedAccounts();
+                Account[] accountArray = new Account[accountList.size()];
+                accountList.toArray(accountArray);
+                return accountArray;
+            }
+        } catch (SQLiteException e) {
+            Log.w(TAG, "Could not get shared accounts for user " + userId, e);
+            return new Account[]{};
         }
     }
 
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 47b65eb..1f88657 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -353,8 +353,8 @@
     }
 
     /** Called when there is a low memory kill */
-    void scheduleNoteLmkdProcKilled(final int pid, final int uid) {
-        mKillHandler.obtainMessage(KillHandler.MSG_LMKD_PROC_KILLED, pid, uid)
+    void scheduleNoteLmkdProcKilled(final int pid, final int uid, final int rssKb) {
+        mKillHandler.obtainMessage(KillHandler.MSG_LMKD_PROC_KILLED, pid, uid, Long.valueOf(rssKb))
                 .sendToTarget();
     }
 
@@ -401,9 +401,9 @@
 
             if (lmkd != null) {
                 updateExistingExitInfoRecordLocked(info, null,
-                        ApplicationExitInfo.REASON_LOW_MEMORY);
+                        ApplicationExitInfo.REASON_LOW_MEMORY, (Long) lmkd.second);
             } else if (zygote != null) {
-                updateExistingExitInfoRecordLocked(info, (Integer) zygote.second, null);
+                updateExistingExitInfoRecordLocked(info, (Integer) zygote.second, null, null);
             } else {
                 scheduleLogToStatsdLocked(info, false);
             }
@@ -486,7 +486,7 @@
      */
     @GuardedBy("mLock")
     private void updateExistingExitInfoRecordLocked(ApplicationExitInfo info,
-            Integer status, Integer reason) {
+            Integer status, Integer reason, Long rssKb) {
         if (info == null || !isFresh(info.getTimestamp())) {
             // if the record is way outdated, don't update it then (because of potential pid reuse)
             return;
@@ -513,6 +513,9 @@
                 immediateLog = true;
             }
         }
+        if (rssKb != null) {
+            info.setRss(rssKb.longValue());
+        }
         scheduleLogToStatsdLocked(info, immediateLog);
     }
 
@@ -523,7 +526,7 @@
      */
     @GuardedBy("mLock")
     private boolean updateExitInfoIfNecessaryLocked(
-            int pid, int uid, Integer status, Integer reason) {
+            int pid, int uid, Integer status, Integer reason, Long rssKb) {
         Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
         if (k != null) {
             uid = k;
@@ -552,7 +555,7 @@
                 // always be the first one we se as `getExitInfosLocked()` returns them sorted
                 // by most-recent-first.
                 isModified[0] = true;
-                updateExistingExitInfoRecordLocked(info, status, reason);
+                updateExistingExitInfoRecordLocked(info, status, reason, rssKb);
                 return FOREACH_ACTION_STOP_ITERATION;
             }
             return FOREACH_ACTION_NONE;
@@ -1668,11 +1671,11 @@
             switch (msg.what) {
                 case MSG_LMKD_PROC_KILLED:
                     mAppExitInfoSourceLmkd.onProcDied(msg.arg1 /* pid */, msg.arg2 /* uid */,
-                            null /* status */);
+                            null /* status */, (Long) msg.obj /* rss_kb */);
                     break;
                 case MSG_CHILD_PROC_DIED:
                     mAppExitInfoSourceZygote.onProcDied(msg.arg1 /* pid */, msg.arg2 /* uid */,
-                            (Integer) msg.obj /* status */);
+                            (Integer) msg.obj /* status */, null /* rss_kb */);
                     break;
                 case MSG_PROC_DIED: {
                     ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
@@ -1833,7 +1836,7 @@
             }
         }
 
-        void onProcDied(final int pid, final int uid, final Integer status) {
+        void onProcDied(final int pid, final int uid, final Integer status, final Long rssKb) {
             if (DEBUG_PROCESSES) {
                 Slog.i(TAG, mTag + ": proc died: pid=" + pid + " uid=" + uid
                         + ", status=" + status);
@@ -1846,8 +1849,12 @@
             // Unlikely but possible: the record has been created
             // Let's update it if we could find a ApplicationExitInfo record
             synchronized (mLock) {
-                if (!updateExitInfoIfNecessaryLocked(pid, uid, status, mPresetReason)) {
-                    addLocked(pid, uid, status);
+                if (!updateExitInfoIfNecessaryLocked(pid, uid, status, mPresetReason, rssKb)) {
+                    if (rssKb != null) {
+                        addLocked(pid, uid, rssKb);     // lmkd
+                    } else {
+                        addLocked(pid, uid, status);    // zygote
+                    }
                 }
 
                 // Notify any interesed party regarding the lmkd kills
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 779aabe..726e827 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -951,12 +951,14 @@
                             try {
                                 switch (inputData.readInt()) {
                                     case LMK_PROCKILL:
-                                        if (receivedLen != 12) {
+                                        if (receivedLen != 16) {
                                             return false;
                                         }
                                         final int pid = inputData.readInt();
                                         final int uid = inputData.readInt();
-                                        mAppExitInfoTracker.scheduleNoteLmkdProcKilled(pid, uid);
+                                        final int rssKb = inputData.readInt();
+                                        mAppExitInfoTracker.scheduleNoteLmkdProcKilled(pid, uid,
+                                                rssKb);
                                         return true;
                                     case LMK_KILL_OCCURRED:
                                         if (receivedLen
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 7a24e9df..1183768 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3692,6 +3692,11 @@
 
         ensureValidStreamType(streamType);
         final int resolvedStream = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
+        if (resolvedStream == -1) {
+            Log.e(TAG, "adjustSuggestedStreamVolume: no stream vol alias for stream type "
+                    + streamType);
+            return;
+        }
 
         // Play sounds on STREAM_RING only.
         if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 &&
@@ -3809,6 +3814,10 @@
         // including with regard to silent mode control (e.g the use of STREAM_RING below and in
         // checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION)
         int streamTypeAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
+        if (streamTypeAlias == -1) {
+            Log.e(TAG,
+                    "adjustStreamVolume: no stream vol alias for stream type " + streamType);
+        }
 
         VolumeStreamState streamState = getVssForStreamOrDefault(streamTypeAlias);
 
@@ -4077,8 +4086,8 @@
         synchronized (mSettingsLock) {
             synchronized (VolumeStreamState.class) {
                 List<Integer> streamsToMute = new ArrayList<>();
-                for (int stream = 0; stream < mStreamStates.size(); stream++) {
-                    final VolumeStreamState vss = mStreamStates.valueAt(stream);
+                for (int streamIdx = 0; streamIdx < mStreamStates.size(); streamIdx++) {
+                    final VolumeStreamState vss = mStreamStates.valueAt(streamIdx);
                     if (vss != null && streamAlias == sStreamVolumeAlias.get(vss.getStreamType())
                             && vss.isMutable()) {
                         if (!(mCameraSoundForced && (vss.getStreamType()
@@ -4219,6 +4228,10 @@
     /*package*/ void onSetStreamVolume(int streamType, int index, int flags, int device,
             String caller, boolean hasModifyAudioSettings, boolean canChangeMute) {
         final int stream = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/-1);
+        if (stream == -1) {
+            Log.e(TAG, "onSetStreamVolume: no stream vol alias for stream type " + stream);
+            return;
+        }
         // setting volume on ui sounds stream type also controls silent mode
         if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
                 (stream == getUiSoundsStreamType())) {
@@ -4868,6 +4881,10 @@
 
         ensureValidStreamType(streamType);
         int streamTypeAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound*/-1);
+        if (streamTypeAlias == -1) {
+            Log.e(TAG, "setStreamVolume: no stream vol alias for stream type " + streamType);
+            return;
+        }
         final VolumeStreamState streamState = getVssForStreamOrDefault(streamTypeAlias);
 
         if (!replaceStreamBtSco() && (streamType == AudioManager.STREAM_VOICE_CALL)
@@ -5586,9 +5603,9 @@
             return new ArrayList<>(Arrays.stream(AudioManager.getPublicStreamTypes())
                     .boxed().toList());
         }
-        ArrayList<Integer> res = new ArrayList(1);
-        for (int stream = 0; stream < sStreamVolumeAlias.size(); ++stream) {
-            final int streamAlias = sStreamVolumeAlias.valueAt(stream);
+        ArrayList<Integer> res = new ArrayList<>(1);
+        for (int streamIdx = 0; streamIdx < sStreamVolumeAlias.size(); ++streamIdx) {
+            final int streamAlias = sStreamVolumeAlias.valueAt(streamIdx);
             if (!res.contains(streamAlias)) {
                 res.add(streamAlias);
             }
@@ -6408,6 +6425,10 @@
                 final int device = getDeviceForStream(streamType);
                 final int streamAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound=*/
                         -1);
+                if (streamAlias == -1) {
+                    Log.e(TAG,
+                            "onUpdateAudioMode: no stream vol alias for stream type " + streamType);
+                }
 
                 if (DEBUG_MODE) {
                     Log.v(TAG, "onUpdateAudioMode: streamType=" + streamType
@@ -7614,8 +7635,8 @@
                 ? MIN_STREAM_VOLUME[AudioSystem.STREAM_ALARM]
                 : Math.min(idx + 1, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
         // update the VolumeStreamState for STREAM_ALARM and its aliases
-        for (int stream = 0; stream < sStreamVolumeAlias.size(); ++stream) {
-            final int streamAlias = sStreamVolumeAlias.valueAt(stream);
+        for (int streamIdx = 0; streamIdx < sStreamVolumeAlias.size(); ++streamIdx) {
+            final int streamAlias = sStreamVolumeAlias.valueAt(streamIdx);
             if (streamAlias == AudioSystem.STREAM_ALARM) {
                 getVssForStreamOrDefault(streamAlias).updateNoPermMinIndex(safeIndex);
             }
@@ -9454,7 +9475,7 @@
         // must be sync'd on mSettingsLock before VolumeStreamState.class
         @GuardedBy("VolumeStreamState.class")
         public void setAllIndexes(VolumeStreamState srcStream, String caller) {
-            if (mStreamType == srcStream.mStreamType) {
+            if (srcStream == null || mStreamType == srcStream.mStreamType) {
                 return;
             }
             int srcStreamType = srcStream.getStreamType();
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 1177be2..8b21d98 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1755,6 +1755,7 @@
         mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
         mTempBrightnessEvent.setPhysicalDisplayName(mPhysicalDisplayName);
         mTempBrightnessEvent.setDisplayState(state);
+        mTempBrightnessEvent.setDisplayStateReason(stateAndReason.second);
         mTempBrightnessEvent.setDisplayPolicy(mPowerRequest.policy);
         mTempBrightnessEvent.setReason(mBrightnessReason);
         mTempBrightnessEvent.setHbmMax(hbmMax);
diff --git a/services/core/java/com/android/server/display/ExternalDisplayStatsService.java b/services/core/java/com/android/server/display/ExternalDisplayStatsService.java
index f6f23d9..608fb35 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayStatsService.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayStatsService.java
@@ -518,18 +518,24 @@
     private void logExternalDisplayIdleStarted() {
         synchronized (mExternalDisplayStates) {
             for (var i = 0; i < mExternalDisplayStates.size(); i++) {
-                mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
-                        KEYGUARD, i + 1, mIsExternalDisplayUsedForAudio);
-                if (DEBUG) {
-                    final int displayId = mExternalDisplayStates.keyAt(i);
-                    final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE);
-                    Slog.d(TAG, "logExternalDisplayIdleStarted"
-                                        + " displayId=" + displayId
-                                        + " currentState=" + state
-                                        + " countOfExternalDisplays=" + (i + 1)
-                                        + " state=" + KEYGUARD
-                                        + " mIsExternalDisplayUsedForAudio="
-                                        + mIsExternalDisplayUsedForAudio);
+                final int displayId = mExternalDisplayStates.keyAt(i);
+                final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE);
+                // Don't try to stop "connected" session by keyguard event.
+                // There is no purpose to measure how long keyguard is shown while external
+                // display is connected but not used for mirroring or extended display.
+                // Therefore there no need to log this event.
+                if (state != DISCONNECTED_STATE && state != CONNECTED_STATE) {
+                    mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
+                            KEYGUARD, i + 1, mIsExternalDisplayUsedForAudio);
+                    if (DEBUG) {
+                        Slog.d(TAG, "logExternalDisplayIdleStarted"
+                                            + " displayId=" + displayId
+                                            + " currentState=" + state
+                                            + " countOfExternalDisplays=" + (i + 1)
+                                            + " state=" + KEYGUARD
+                                            + " mIsExternalDisplayUsedForAudio="
+                                            + mIsExternalDisplayUsedForAudio);
+                    }
                 }
             }
         }
@@ -540,7 +546,11 @@
             for (var i = 0; i < mExternalDisplayStates.size(); i++) {
                 final int displayId = mExternalDisplayStates.keyAt(i);
                 final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE);
-                if (state == DISCONNECTED_STATE) {
+                // No need to restart "connected" session after keyguard is stopped.
+                // This is because the connection is continuous even if keyguard is shown.
+                // In case in the future keyguard needs to be measured also while display
+                // is not used, then a 'keyguard finished' event needs to be emitted in this case.
+                if (state == DISCONNECTED_STATE || state == CONNECTED_STATE) {
                     return;
                 }
                 mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index 5cc603c..ad57ebf 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -52,6 +52,8 @@
     private String mPhysicalDisplayId;
     private String mPhysicalDisplayName;
     private int mDisplayState;
+    @Display.StateReason
+    private int mDisplayStateReason;
     private int mDisplayPolicy;
     private long mTime;
     private float mLux;
@@ -96,6 +98,7 @@
         mPhysicalDisplayId = that.getPhysicalDisplayId();
         mPhysicalDisplayName = that.getPhysicalDisplayName();
         mDisplayState = that.mDisplayState;
+        mDisplayStateReason = that.mDisplayStateReason;
         mDisplayPolicy = that.mDisplayPolicy;
         mTime = that.getTime();
         // Lux values
@@ -133,6 +136,7 @@
         mPhysicalDisplayId = "";
         mPhysicalDisplayName = "";
         mDisplayState = Display.STATE_UNKNOWN;
+        mDisplayStateReason = Display.STATE_REASON_UNKNOWN;
         mDisplayPolicy = POLICY_OFF;
         // Lux values
         mLux = INVALID_LUX;
@@ -176,6 +180,7 @@
                 && mPhysicalDisplayId.equals(that.mPhysicalDisplayId)
                 && mPhysicalDisplayName.equals(that.mPhysicalDisplayName)
                 && mDisplayState == that.mDisplayState
+                && mDisplayStateReason == that.mDisplayStateReason
                 && mDisplayPolicy == that.mDisplayPolicy
                 && Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux)
                 && Float.floatToRawIntBits(mPreThresholdLux)
@@ -221,6 +226,7 @@
                 + ", reason=" + mReason.toString(mAdjustmentFlags)
                 + ", strat=" + mDisplayBrightnessStrategyName
                 + ", state=" + Display.stateToString(mDisplayState)
+                + ", stateReason=" + Display.stateReasonToString(mDisplayStateReason)
                 + ", policy=" + policyToString(mDisplayPolicy)
                 + ", flags=" + flagsToString()
                 // Autobrightness
@@ -293,6 +299,10 @@
         mDisplayState = state;
     }
 
+    public void setDisplayStateReason(@Display.StateReason int reason) {
+        mDisplayStateReason = reason;
+    }
+
     public void setDisplayPolicy(int policy) {
         mDisplayPolicy = policy;
     }
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 886857c..d43e783 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -544,11 +544,10 @@
 
     private void startDozingInternal(IBinder token, int screenState,
             @Display.StateReason int reason, int screenBrightness) {
-        if (DEBUG) {
-            Slog.d(TAG, "Dream requested to start dozing: " + token
-                    + ", screenState=" + screenState
-                    + ", screenBrightness=" + screenBrightness);
-        }
+        Slog.d(TAG, "Dream requested to start dozing: " + token
+                + ", screenState=" + Display.stateToString(screenState)
+                + ", reason=" + Display.stateReasonToString(reason)
+                + ", screenBrightness=" + screenBrightness);
 
         synchronized (mLock) {
             if (mCurrentDream != null && mCurrentDream.token == token && mCurrentDream.canDoze) {
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 4993412..1d1a178 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -22,6 +22,8 @@
 import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
 import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT;
 
+import static com.android.hardware.input.Flags.keyboardLayoutManagerMultiUserImeSetup;
+
 import android.annotation.AnyThread;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
@@ -1066,9 +1068,15 @@
             for (InputMethodInfo imeInfo :
                     inputMethodManagerInternal.getEnabledInputMethodListAsUser(
                             userId)) {
-                for (InputMethodSubtype imeSubtype :
-                        inputMethodManager.getEnabledInputMethodSubtypeList(
-                                imeInfo, true /* allowsImplicitlyEnabledSubtypes */)) {
+                final List<InputMethodSubtype> imeSubtypes;
+                if (keyboardLayoutManagerMultiUserImeSetup()) {
+                    imeSubtypes = inputMethodManagerInternal.getEnabledInputMethodSubtypeListAsUser(
+                            imeInfo.getId(), true /* allowsImplicitlyEnabledSubtypes */, userId);
+                } else {
+                    imeSubtypes = inputMethodManager.getEnabledInputMethodSubtypeList(imeInfo,
+                            true /* allowsImplicitlyEnabledSubtypes */);
+                }
+                for (InputMethodSubtype imeSubtype : imeSubtypes) {
                     if (!imeSubtype.isSuitableForPhysicalKeyboardLayoutMapping()) {
                         continue;
                     }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 64b6dbe..4716e6c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -271,7 +271,6 @@
 
     private static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
 
-    private static final int MSG_SYSTEM_UNLOCK_USER = 5000;
     private static final int MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED = 5010;
 
     private static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000;
@@ -760,7 +759,6 @@
                             .getMethodMap();
 
             synchronized (ImfLock.class) {
-                final boolean isCurrentUser = (userId == mCurrentUserId);
                 final AdditionalSubtypeMap additionalSubtypeMap =
                         AdditionalSubtypeMapRepository.get(userId);
                 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
@@ -812,9 +810,6 @@
                 final InputMethodSettings newSettings =
                         InputMethodSettings.create(newMethodMap, userId);
                 InputMethodSettingsRepository.put(userId, newSettings);
-                if (!isCurrentUser) {
-                    return;
-                }
                 postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
 
                 boolean changed = false;
@@ -1024,10 +1019,24 @@
 
         @Override
         public void onUserUnlocking(@NonNull TargetUser user) {
-            // Called on ActivityManager thread.
-            SecureSettingsWrapper.onUserUnlocking(user.getUserIdentifier());
-            mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER, user.getUserIdentifier(), 0)
-                    .sendToTarget();
+            // Called on ActivityManager thread. Do not block the calling thread.
+            final int userId = user.getUserIdentifier();
+            SecureSettingsWrapper.onUserUnlocking(userId);
+            mService.mIoHandler.post(() -> {
+                final var settings = queryInputMethodServicesInternal(mService.mContext, userId,
+                        AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
+                InputMethodSettingsRepository.put(userId, settings);
+                synchronized (ImfLock.class) {
+                    if (!mService.mSystemReady) {
+                        return;
+                    }
+                    // We need to rebuild IMEs.
+                    mService.postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */,
+                            userId);
+                    mService.updateInputMethodsFromSettingsLocked(true /* enabledChanged */,
+                            userId);
+                }
+            });
         }
 
         @Override
@@ -1078,23 +1087,6 @@
         }
     }
 
-    void onUnlockUser(@UserIdInt int userId) {
-        synchronized (ImfLock.class) {
-            if (DEBUG) {
-                Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + mCurrentUserId);
-            }
-            if (!mSystemReady) {
-                return;
-            }
-            final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
-                    userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
-            InputMethodSettingsRepository.put(userId, newSettings);
-            // We need to rebuild IMEs.
-            postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
-            updateInputMethodsFromSettingsLocked(true /* enabledChanged */, userId);
-        }
-    }
-
     @GuardedBy("ImfLock.class")
     void scheduleSwitchUserTaskLocked(@UserIdInt int userId,
             @Nullable IInputMethodClientInvoker clientToBeReset) {
@@ -3966,6 +3958,10 @@
     @Override
     public void showInputMethodPickerFromClient(IInputMethodClient client,
             int auxiliarySubtypeMode) {
+        if (mConcurrentMultiUserModeEnabled) {
+            Slog.w(TAG, "showInputMethodPickerFromClient is not enabled on automotive");
+            return;
+        }
         final int callingUserId = UserHandle.getCallingUserId();
         synchronized (ImfLock.class) {
             if (!canShowInputMethodPickerLocked(client)) {
@@ -5135,11 +5131,6 @@
                     sendOnNavButtonFlagsChangedToAllImesLocked();
                 }
                 return true;
-            case MSG_SYSTEM_UNLOCK_USER: {
-                final int userId = msg.arg1;
-                onUnlockUser(userId);
-                return true;
-            }
             case MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED: {
                 final int userId = msg.arg1;
                 final List<InputMethodInfo> imes = (List<InputMethodInfo>) msg.obj;
@@ -5863,22 +5854,7 @@
                 if (!settings.getMethodMap().containsKey(imeId)) {
                     return false; // IME is not found.
                 }
-                if (userId == mCurrentUserId) {
-                    setInputMethodEnabledLocked(imeId, enabled, userId);
-                    return true;
-                }
-                if (enabled) {
-                    final String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
-                    final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(
-                            enabledImeIdsStr, imeId);
-                    if (!TextUtils.equals(enabledImeIdsStr, newEnabledImeIdsStr)) {
-                        settings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
-                    }
-                } else {
-                    settings.buildAndPutEnabledInputMethodsStrRemovingId(
-                            new StringBuilder(),
-                            settings.getEnabledInputMethodsAndSubtypeList(), imeId);
-                }
+                setInputMethodEnabledLocked(imeId, enabled, userId);
                 return true;
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 6b7b702..5e45b4c 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -869,18 +869,25 @@
     private int createDraftSession(String packageName, String installerPackage,
             String callerPackageName,
             IntentSender statusReceiver, int userId) throws IOException {
+        Computer snapshot = mPm.snapshotComputer();
         PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
                 PackageInstaller.SessionParams.MODE_FULL_INSTALL);
         sessionParams.setAppPackageName(packageName);
         sessionParams.setAppLabel(
                 mContext.getString(com.android.internal.R.string.unarchival_session_app_label));
-        sessionParams.setAppIcon(
-                getArchivedAppIcon(packageName, UserHandle.of(userId), callerPackageName));
+        // The draft session's app icon is based on the current launcher's icon overlay appops mode
+        String launcherPackageName = getCurrentLauncherPackageName(userId);
+        int launcherUid = launcherPackageName != null
+                ? snapshot.getPackageUid(launcherPackageName, 0, userId)
+                : Process.SYSTEM_UID;
+        sessionParams.setAppIcon(getArchivedAppIcon(packageName, UserHandle.of(userId),
+                isOverlayEnabled(launcherUid,
+                        launcherPackageName == null ? callerPackageName : launcherPackageName)));
         // To make sure SessionInfo::isUnarchival returns true for draft sessions,
         // INSTALL_UNARCHIVE is also set.
         sessionParams.installFlags = (INSTALL_UNARCHIVE_DRAFT | INSTALL_UNARCHIVE);
 
-        int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
+        int installerUid = snapshot.getPackageUid(installerPackage, 0, userId);
         // Handles case of repeated unarchival calls for the same package.
         int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid,
                 sessionParams,
@@ -926,12 +933,27 @@
     /**
      * Returns the icon of an archived app. This is the icon of the main activity of the app.
      *
-     * <p> The icon is returned without any treatment/overlay. In the rare case the app had multiple
-     * launcher activities, only one of the icons is returned arbitrarily.
+     * <p> In the rare case the app had multiple launcher activities, only one of the icons is
+     * returned arbitrarily.
+     *
+     * <p> By default, the icon will be overlay'd with a cloud icon on top. A launcher app can
+     * disable the cloud overlay via the
+     * {@link LauncherApps.ArchiveCompatibilityParams#setEnableIconOverlay(boolean)} API.
+     * The default launcher's cloud overlay mode determines the cloud overlay status returned by
+     * any other callers. That is, if the current launcher has the cloud overlay disabled, any other
+     * app that fetches the app icon will also get an icon that has the cloud overlay disabled.
+     * This is to prevent style mismatch caused by icons that are fetched by different callers.
      */
     @Nullable
     public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
             String callingPackageName) {
+        return getArchivedAppIcon(packageName, user,
+                isOverlayEnabled(Binder.getCallingUid(), callingPackageName));
+    }
+
+    @Nullable
+    private Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
+            boolean isOverlayEnabled) {
         Objects.requireNonNull(packageName);
         Objects.requireNonNull(user);
 
@@ -955,14 +977,19 @@
         // In the rare case the archived app defined more than two launcher activities, we choose
         // the first one arbitrarily.
         Bitmap icon = decodeIcon(archiveState.getActivityInfos().get(0));
-        if (icon != null && getAppOpsManager().checkOp(
-                AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, callingPackageName)
-                == MODE_ALLOWED) {
+
+        if (icon != null && isOverlayEnabled) {
             icon = includeCloudOverlay(icon);
         }
         return icon;
     }
 
+    private boolean isOverlayEnabled(int callingUid, String packageName) {
+        return getAppOpsManager().checkOp(
+                AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, packageName)
+                == MODE_ALLOWED;
+    }
+
     /**
      * This method first checks the ArchiveState for the provided userId and then tries to fallback
      * to other users if the current user is not archived.
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 613ebd3..46207c1 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -579,11 +579,11 @@
                         + " to user %d on start", userId, displayId, userAssignedToDisplay);
                 return false;
             }
-            // Then if was assigned extra
-            userAssignedToDisplay = mExtraDisplaysAssignedToUsers.get(userId, USER_NULL);
+            // Then if the display was assigned before
+            userAssignedToDisplay = mExtraDisplaysAssignedToUsers.get(displayId, USER_NULL);
             if (userAssignedToDisplay != USER_NULL) {
                 Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): failed because user %d was already "
-                        + "assigned that extra display", userId, displayId, userAssignedToDisplay);
+                        + "assigned to extra display", userId, displayId, userAssignedToDisplay);
                 return false;
             }
             if (DBG) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a84598d..1c14c5d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5522,7 +5522,7 @@
             final int procCount = procs.size();
             for (int i = 0; i < procCount; i++) {
                 final int procUid = procs.keyAt(i);
-                if (UserHandle.isApp(procUid) || !UserHandle.isSameUser(procUid, uid)) {
+                if (!UserHandle.isCore(procUid) || !UserHandle.isSameUser(procUid, uid)) {
                     // Don't use an app process or different user process for system component.
                     continue;
                 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 1efb3ef..475b473 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -550,7 +550,6 @@
 
     /** Save allocating when calculating rects */
     private final Rect mTmpRect = new Rect();
-    private final Rect mTmpRect2 = new Rect();
     private final Region mTmpRegion = new Region();
 
     private final Configuration mTmpConfiguration = new Configuration();
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index e6dac88..dab3978 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -354,6 +354,9 @@
 
     private static void createAndUploadReport(ProfcollectForwardingService pfs) {
         BackgroundThread.get().getThreadHandler().post(() -> {
+            if (pfs.mIProfcollect == null) {
+                return;
+            }
             String reportName;
             try {
                 reportName = pfs.mIProfcollect.report(pfs.mUsageSetting) + ".zip";
@@ -401,6 +404,9 @@
                 final int traceDuration = 5000;
                 final String traceTag = "camera";
                 BackgroundThread.get().getThreadHandler().post(() -> {
+                    if (mIProfcollect == null) {
+                        return;
+                    }
                     try {
                         mIProfcollect.trace_process(traceTag, "android.hardware.camera.provider",
                                 traceDuration);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java
index 98ba9ae..abb354b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java
@@ -151,9 +151,10 @@
     }
 
     @Test
-    public void testDisplayInteractivityChanges(
+    public void testDisplayInteractivityChangesWhileMirroring(
             @TestParameter final boolean isExternalDisplayUsedForAudio) {
         mExternalDisplayStatsService.onDisplayConnected(mMockedLogicalDisplay);
+        mExternalDisplayStatsService.onDisplayAdded(EXTERNAL_DISPLAY_ID);
         mHandler.flush();
         assertThat(mInteractivityReceiver).isNotNull();
 
@@ -180,12 +181,38 @@
         mInteractivityReceiver.onReceive(null, null);
         assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isTrue();
         verify(mMockedInjector).writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
-                FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__CONNECTED,
+                FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__MIRRORING,
                 /*numberOfDisplays=*/ 1,
                 isExternalDisplayUsedForAudio);
     }
 
     @Test
+    public void testDisplayInteractivityChangesWhileConnected() {
+        mExternalDisplayStatsService.onDisplayConnected(mMockedLogicalDisplay);
+        mHandler.flush();
+        assertThat(mInteractivityReceiver).isNotNull();
+        clearInvocations(mMockedInjector);
+
+        // Default is 'interactive', so no log should be written.
+        mInteractivityReceiver.onReceive(null, null);
+        assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isTrue();
+        verify(mMockedInjector, never()).writeLog(anyInt(), anyInt(), anyInt(), anyBoolean());
+
+        // Change to non-interactive should not produce log
+        when(mMockedInjector.isInteractive(eq(EXTERNAL_DISPLAY_ID))).thenReturn(false);
+        mInteractivityReceiver.onReceive(null, null);
+        assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isFalse();
+        verify(mMockedInjector, never()).writeLog(anyInt(), anyInt(), anyInt(), anyBoolean());
+        clearInvocations(mMockedInjector);
+
+        // Change back to interactive should not produce log
+        when(mMockedInjector.isInteractive(eq(EXTERNAL_DISPLAY_ID))).thenReturn(true);
+        mInteractivityReceiver.onReceive(null, null);
+        assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isTrue();
+        verify(mMockedInjector, never()).writeLog(anyInt(), anyInt(), anyInt(), anyBoolean());
+    }
+
+    @Test
     public void testAudioPlaybackChanges() {
         mExternalDisplayStatsService.onDisplayConnected(mMockedLogicalDisplay);
         mHandler.flush();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
index 26f6e91..df09b04 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
@@ -46,6 +46,7 @@
         mBrightnessEvent.setPhysicalDisplayId("987654321");
         mBrightnessEvent.setPhysicalDisplayName("display_name");
         mBrightnessEvent.setDisplayState(Display.STATE_ON);
+        mBrightnessEvent.setDisplayStateReason(Display.STATE_REASON_DEFAULT_POLICY);
         mBrightnessEvent.setDisplayPolicy(POLICY_BRIGHT);
         mBrightnessEvent.setLux(100.0f);
         mBrightnessEvent.setPercent(46.5f);
@@ -82,9 +83,9 @@
         String actualString = mBrightnessEvent.toString(false);
         String expectedString =
                 "BrightnessEvent: brt=0.6 (46.5%), nits= 893.8, lux=100.0, reason=doze [ "
-                        + "low_pwr ], strat=strategy_name, state=ON, policy=BRIGHT, flags=, "
-                        + "initBrt=25.0, rcmdBrt=0.6, preBrt=NaN, preLux=150.0, "
-                        + "wasShortTermModelActive=true, autoBrightness=true (idle), "
+                        + "low_pwr ], strat=strategy_name, state=ON, stateReason=DEFAULT_POLICY, "
+                        + "policy=BRIGHT, flags=, initBrt=25.0, rcmdBrt=0.6, preBrt=NaN, "
+                        + "preLux=150.0, wasShortTermModelActive=true, autoBrightness=true (idle), "
                         + "unclampedBrt=0.65, hbmMax=0.62, hbmMode=off, thrmMax=0.65, "
                         + "rbcStrength=-1, powerFactor=0.2, physDisp=display_name(987654321), "
                         + "logicalId=1";
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index adcbf5c..194bf4b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -442,20 +442,24 @@
                 IMPORTANCE_FOREGROUND_SERVICE,       // importance
                 null);                               // description
 
-        // Case 4: Create a process from another package with kill from lmkd
+        /*
+         * Case 4: Create a process from another package with kill from lmkd
+         * We expect LMKD's reported RSS to be the process' last seen RSS.
+         */
         final int app2UidUser2 = 1010234;
         final int app2PidUser2 = 12348;
         final long app2Pss1 = 54321;
         final long app2Rss1 = 65432;
+        final long lmkd_reported_rss = 43215;
         final String app2ProcessName = "com.android.test.stub2:process";
         final String app2PackageName = "com.android.test.stub2";
 
         sleep(1);
         final long now4 = System.currentTimeMillis();
-        doReturn(new Pair<Long, Object>(now4, Integer.valueOf(0)))
+        doReturn(null)
                 .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
                 .remove(anyInt(), anyInt());
-        doReturn(new Pair<Long, Object>(now4, null))
+        doReturn(new Pair<Long, Object>(now4, Long.valueOf(lmkd_reported_rss)))
                 .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
                 .remove(anyInt(), anyInt());
 
@@ -490,7 +494,7 @@
                 null,                                     // subReason
                 0,                                        // status
                 app2Pss1,                                 // pss
-                app2Rss1,                                 // rss
+                lmkd_reported_rss,                        // rss
                 IMPORTANCE_CACHED,                        // importance
                 null);                                    // description
 
@@ -499,6 +503,11 @@
         mAppExitInfoTracker.getExitInfo(null, app2UidUser2, 0, 0, list);
         assertEquals(1, list.size());
 
+        info = list.get(0);
+
+        // Verify the AppExitInfo has the LMKD reported RSS
+        assertEquals(lmkd_reported_rss, info.getRss());
+
         // Case 5: App native crash
         final int app3UidUser2 = 1010345;
         final int app3PidUser2 = 12349;
@@ -599,7 +608,7 @@
                 null,                                     // subReason
                 0,                                        // status
                 app2Pss1,                                 // pss
-                app2Rss1,                                 // rss
+                lmkd_reported_rss,                        // rss
                 IMPORTANCE_CACHED,                        // importance
                 null);                                    // description
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
index 280fe4c..34f7ebb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
@@ -66,6 +66,7 @@
 import org.junit.Test;
 import org.junit.rules.TestName;
 
+import java.time.Duration;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -131,7 +132,7 @@
         assertTrue("Failed to wait for transaction to get committed",
                 countDownLatch.await(WAIT_TIME_S, TimeUnit.SECONDS));
         assertTrue("Failed to wait for stable geometry",
-                waitForStableWindowGeometry(WAIT_TIME_S, TimeUnit.SECONDS));
+                waitForStableWindowGeometry(Duration.ofSeconds(WAIT_TIME_S)));
 
         ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(secureSC)
                 .setCaptureSecureLayers(true)
@@ -212,7 +213,7 @@
         assertTrue("Failed to wait for transaction to get committed",
                 countDownLatch.await(WAIT_TIME_S, TimeUnit.SECONDS));
         assertTrue("Failed to wait for stable geometry",
-                waitForStableWindowGeometry(WAIT_TIME_S, TimeUnit.SECONDS));
+                waitForStableWindowGeometry(Duration.ofSeconds(WAIT_TIME_S)));
 
         ScreenshotHardwareBuffer[] screenCapture = new ScreenshotHardwareBuffer[1];
         Bitmap screenshot = null;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index d5d2847..b92af87 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -77,6 +77,7 @@
 import android.view.SurfaceControl;
 
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.internal.os.BackgroundThread;
 import com.android.server.AnimationThread;
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
@@ -553,6 +554,9 @@
         // This is a different handler object than the wm.mAnimationHandler above.
         waitHandlerIdle(AnimationThread.getHandler());
         waitHandlerIdle(SurfaceAnimationThread.getHandler());
+        // Some binder calls are posted to BackgroundThread.getHandler(), we should wait for them
+        // to finish to run next test.
+        waitHandlerIdle(BackgroundThread.getHandler());
     }
 
     static void waitHandlerIdle(Handler handler) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
index f1d84cf..529e9b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
@@ -49,6 +49,7 @@
 import org.junit.Test;
 import org.junit.rules.TestName;
 
+import java.time.Duration;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -144,7 +145,7 @@
                         }
                     }
                     return false;
-                }, TIMEOUT_S, TimeUnit.SECONDS);
+                }, Duration.ofSeconds(TIMEOUT_S));
 
         assertAndDumpWindowState(TAG, "Failed to find window or was not marked trusted",
                 foundTrusted[0]);
@@ -209,7 +210,7 @@
                         }
                     }
                     return foundTrusted[0] && foundTrusted[1];
-                }, TIMEOUT_S, TimeUnit.SECONDS);
+                }, Duration.ofSeconds(TIMEOUT_S));
 
         if (!foundTrusted[0] || !foundTrusted[1]) {
             CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, mName.getMethodName());
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 8d1fc50..d32cedb 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -40,7 +40,7 @@
 import com.android.cts.input.DebugInputRule
 import com.android.cts.input.UinputTouchScreen
 
-import java.util.concurrent.TimeUnit
+import java.time.Duration
 
 import org.junit.After
 import org.junit.Assert.assertEquals
@@ -193,6 +193,6 @@
         val flags = " -W -n "
         val startCmd = "am start $flags $PACKAGE_NAME/.UnresponsiveGestureMonitorActivity"
         instrumentation.uiAutomation.executeShellCommand(startCmd)
-        waitForStableWindowGeometry(5L, TimeUnit.SECONDS)
+        waitForStableWindowGeometry(Duration.ofSeconds(5))
     }
 }
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
index 4c531b8..a4085e5 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
@@ -23,6 +23,7 @@
     resource_dirs: ["res"],
     libs: ["android.test.runner"],
     static_libs: [
+        "androidx.core_core",
         "androidx.test.ext.junit",
         "androidx.test.rules",
         "compatibility-device-util-axt",
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 2df9418..45bf8e3 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -690,7 +690,9 @@
         resource_format = item_iter->second.format;
       }
 
-      if (!ParseItem(parser, out_resource, resource_format)) {
+      // Don't bother parsing the item if it is behind a disabled flag
+      if (out_resource->flag_status != FlagStatus::Disabled &&
+          !ParseItem(parser, out_resource, resource_format)) {
         return false;
       }
       return true;
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index b59b165..2e6ad13 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -69,8 +69,13 @@
     return TestParse(str, ConfigDescription{});
   }
 
-  ::testing::AssertionResult TestParse(StringPiece str, const ConfigDescription& config) {
-    ResourceParserOptions parserOptions;
+  ::testing::AssertionResult TestParse(StringPiece str, ResourceParserOptions parserOptions) {
+    return TestParse(str, ConfigDescription{}, parserOptions);
+  }
+
+  ::testing::AssertionResult TestParse(
+      StringPiece str, const ConfigDescription& config,
+      ResourceParserOptions parserOptions = ResourceParserOptions()) {
     ResourceParser parser(context_->GetDiagnostics(), &table_, android::Source{"test"}, config,
                           parserOptions);
 
@@ -242,6 +247,19 @@
   EXPECT_FALSE(TestParse(R"(<string name="foo4" translatable="yes">Translate</string>)"));
 }
 
+TEST_F(ResourceParserTest, ParseStringBehindDisabledFlag) {
+  FeatureFlagProperties flag_properties(true, false);
+  ResourceParserOptions options;
+  options.feature_flag_values = {{"falseFlag", flag_properties}};
+  ASSERT_TRUE(TestParse(
+      R"(<string name="foo" android:featureFlag="falseFlag"
+              xmlns:android="http://schemas.android.com/apk/res/android">foo</string>)",
+      options));
+
+  String* str = test::GetValue<String>(&table_, "string/foo");
+  ASSERT_THAT(str, IsNull());
+}
+
 TEST_F(ResourceParserTest, IgnoreXliffTagsOtherThanG) {
   std::string input = R"(
       <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
index 7806061..b4a7663 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
@@ -86,11 +86,12 @@
     @Test
     public void testAllFieldsValidV1() throws Exception {
         System.out.println("starting testAllFieldsValidV1.");
-        new AppInfoFactory()
-                .createFromOdElement(
-                        TestUtils.getElementFromResource(
-                                Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_V1_FILE_NAME)),
-                        1L);
+        var unused =
+                new AppInfoFactory()
+                        .createFromOdElement(
+                                TestUtils.getElementFromResource(
+                                        Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_V1_FILE_NAME)),
+                                1L);
     }
 
     /** Test for unrecognized field v1. */
@@ -133,7 +134,7 @@
                     TestUtils.getElementFromResource(
                             Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_V1_FILE_NAME));
             TestUtils.removeOdChildEleWithName(appInfoEle, optField);
-            new AppInfoFactory().createFromOdElement(appInfoEle, 1L);
+            var unused = new AppInfoFactory().createFromOdElement(appInfoEle, 1L);
         }
     }
 
@@ -202,7 +203,7 @@
                             Paths.get(APP_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
             ele.removeAttribute(optField);
             AppInfo appInfo = new AppInfoFactory().createFromHrElement(ele, DEFAULT_VERSION);
-            appInfo.toOdDomElement(TestUtils.document());
+            var unused = appInfo.toOdDomElement(TestUtils.document());
         }
 
         for (String optField : OPTIONAL_FIELD_NAMES_OD) {
@@ -211,7 +212,7 @@
                             Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
             TestUtils.removeOdChildEleWithName(ele, optField);
             AppInfo appInfo = new AppInfoFactory().createFromOdElement(ele, DEFAULT_VERSION);
-            appInfo.toHrDomElement(TestUtils.document());
+            var unused = appInfo.toHrDomElement(TestUtils.document());
         }
     }
 
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
index a4472b1..2746800 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
@@ -99,7 +99,7 @@
             developerInfoEle.removeAttribute(optField);
             DeveloperInfo developerInfo =
                     new DeveloperInfoFactory().createFromHrElement(developerInfoEle);
-            developerInfo.toOdDomElement(TestUtils.document());
+            var unused = developerInfo.toOdDomElement(TestUtils.document());
         }
 
         for (String optField : OPTIONAL_FIELD_NAMES_OD) {
@@ -109,7 +109,7 @@
             TestUtils.removeOdChildEleWithName(developerInfoEle, optField);
             DeveloperInfo developerInfo =
                     new DeveloperInfoFactory().createFromOdElement(developerInfoEle);
-            developerInfo.toHrDomElement(TestUtils.document());
+            var unused = developerInfo.toHrDomElement(TestUtils.document());
         }
     }
 
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
index 9d197a2..27f8720 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
@@ -64,7 +64,7 @@
                             Paths.get(SECURITY_LABELS_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
             ele.removeAttribute(optField);
             SecurityLabels securityLabels = new SecurityLabelsFactory().createFromHrElement(ele);
-            securityLabels.toOdDomElement(TestUtils.document());
+            var unused = securityLabels.toOdDomElement(TestUtils.document());
         }
         for (String optField : OPTIONAL_FIELD_NAMES_OD) {
             var ele =
@@ -72,7 +72,7 @@
                             Paths.get(SECURITY_LABELS_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
             TestUtils.removeOdChildEleWithName(ele, optField);
             SecurityLabels securityLabels = new SecurityLabelsFactory().createFromOdElement(ele);
-            securityLabels.toHrDomElement(TestUtils.document());
+            var unused = securityLabels.toHrDomElement(TestUtils.document());
         }
     }