Merge "Remove VibrationAttributes#CATEGORY_KEYBOARD (in framework)" into main
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 42d66ce..f7745d1 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -325,17 +325,62 @@
 
     private String mTag = TAG;
 
-    private final ISurfaceControlViewHostParent mSurfaceControlViewHostParent =
-            new ISurfaceControlViewHostParent.Stub() {
+    private static class SurfaceControlViewHostParent extends ISurfaceControlViewHostParent.Stub {
+
+        /**
+         * mSurfaceView is set in {@link #attach} and cleared in {@link #detach} to prevent
+         * temporary memory leaks. The remote process's ISurfaceControlViewHostParent binder
+         * reference extends this object's lifetime. If mSurfaceView is not cleared in
+         * {@link #detach}, then the SurfaceView and anything it references will not be promptly
+         * garbage collected.
+         */
+        @Nullable
+        private SurfaceView mSurfaceView;
+
+        void attach(SurfaceView sv) {
+            synchronized (this) {
+                try {
+                    sv.mSurfacePackage.getRemoteInterface().attachParentInterface(this);
+                    mSurfaceView = sv;
+                } catch (RemoteException e) {
+                    Log.d(TAG, "Failed to attach parent interface to SCVH. Likely SCVH is alraedy "
+                            + "dead.");
+                }
+            }
+        }
+
+        void detach() {
+            synchronized (this) {
+                if (mSurfaceView == null) {
+                    return;
+                }
+                try {
+                    mSurfaceView.mSurfacePackage.getRemoteInterface().attachParentInterface(null);
+                } catch (RemoteException e) {
+                    Log.d(TAG, "Failed to remove parent interface from SCVH. Likely SCVH is "
+                            + "already dead");
+                }
+                mSurfaceView = null;
+            }
+        }
+
         @Override
         public void updateParams(WindowManager.LayoutParams[] childAttrs) {
-            mEmbeddedWindowParams.clear();
-            mEmbeddedWindowParams.addAll(Arrays.asList(childAttrs));
+            SurfaceView sv;
+            synchronized (this) {
+                sv = mSurfaceView;
+            }
+            if (sv == null) {
+                return;
+            }
 
-            if (isAttachedToWindow()) {
-                runOnUiThread(() -> {
-                    if (mParent != null) {
-                        mParent.recomputeViewAttributes(SurfaceView.this);
+            sv.mEmbeddedWindowParams.clear();
+            sv.mEmbeddedWindowParams.addAll(Arrays.asList(childAttrs));
+
+            if (sv.isAttachedToWindow()) {
+                sv.runOnUiThread(() -> {
+                    if (sv.mParent != null) {
+                        sv.mParent.recomputeViewAttributes(sv);
                     }
                 });
             }
@@ -343,34 +388,45 @@
 
         @Override
         public void forwardBackKeyToParent(@NonNull KeyEvent keyEvent) {
-                runOnUiThread(() -> {
-                    if (!isAttachedToWindow() || keyEvent.getKeyCode() != KeyEvent.KEYCODE_BACK) {
-                        return;
-                    }
-                    final ViewRootImpl vri = getViewRootImpl();
-                    if (vri == null) {
-                        return;
-                    }
-                    final InputManager inputManager = mContext.getSystemService(InputManager.class);
-                    if (inputManager == null) {
-                        return;
-                    }
-                    // Check that the event was created recently.
-                    final long timeDiff = SystemClock.uptimeMillis() - keyEvent.getEventTime();
-                    if (timeDiff > FORWARD_BACK_KEY_TOLERANCE_MS) {
-                        Log.e(TAG, "Ignore the input event that exceed the tolerance time, "
-                                + "exceed " + timeDiff + "ms");
-                        return;
-                    }
-                    if (inputManager.verifyInputEvent(keyEvent) == null) {
-                        Log.e(TAG, "Received invalid input event");
-                        return;
-                    }
-                    vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */,
-                            true /* processImmediately */);
-                });
+            SurfaceView sv;
+            synchronized (this) {
+                sv = mSurfaceView;
+            }
+            if (sv == null) {
+                return;
+            }
+
+            sv.runOnUiThread(() -> {
+                if (!sv.isAttachedToWindow() || keyEvent.getKeyCode() != KeyEvent.KEYCODE_BACK) {
+                    return;
+                }
+                final ViewRootImpl vri = sv.getViewRootImpl();
+                if (vri == null) {
+                    return;
+                }
+                final InputManager inputManager = sv.mContext.getSystemService(InputManager.class);
+                if (inputManager == null) {
+                    return;
+                }
+                // Check that the event was created recently.
+                final long timeDiff = SystemClock.uptimeMillis() - keyEvent.getEventTime();
+                if (timeDiff > FORWARD_BACK_KEY_TOLERANCE_MS) {
+                    Log.e(TAG, "Ignore the input event that exceed the tolerance time, "
+                            + "exceed " + timeDiff + "ms");
+                    return;
+                }
+                if (inputManager.verifyInputEvent(keyEvent) == null) {
+                    Log.e(TAG, "Received invalid input event");
+                    return;
+                }
+                vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */,
+                        true /* processImmediately */);
+            });
         }
-    };
+    }
+
+    private final SurfaceControlViewHostParent mSurfaceControlViewHostParent =
+            new SurfaceControlViewHostParent();
 
     private final boolean mRtDrivenClipping = Flags.clipSurfaceviews();
 
@@ -930,13 +986,8 @@
             }
 
             if (mSurfacePackage != null) {
-                try {
-                    mSurfacePackage.getRemoteInterface().attachParentInterface(null);
-                    mEmbeddedWindowParams.clear();
-                } catch (RemoteException e) {
-                    Log.d(TAG, "Failed to remove parent interface from SCVH. Likely SCVH is "
-                            + "already dead");
-                }
+                mSurfaceControlViewHostParent.detach();
+                mEmbeddedWindowParams.clear();
                 if (releaseSurfacePackage) {
                     mSurfacePackage.release();
                     mSurfacePackage = null;
@@ -2067,12 +2118,7 @@
             applyTransactionOnVriDraw(transaction);
         }
         mSurfacePackage = p;
-        try {
-            mSurfacePackage.getRemoteInterface().attachParentInterface(
-                    mSurfaceControlViewHostParent);
-        } catch (RemoteException e) {
-            Log.d(TAG, "Failed to attach parent interface to SCVH. Likely SCVH is already dead.");
-        }
+        mSurfaceControlViewHostParent.attach(this);
 
         if (isFocused()) {
             requestEmbeddedFocus(true);
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 33610a0..c7e1fba 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -396,6 +396,9 @@
         int cujType = conf.mCujType;
         if (!shouldMonitor()) {
             return false;
+        } else if (!conf.hasValidView()) {
+            Log.w(TAG, "The view has since become invalid, aborting the CUJ.");
+            return false;
         }
 
         RunningTracker tracker = putTrackerIfNoCurrent(cujType, () ->
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 4aeee6c..cbcbf2db 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -736,7 +736,7 @@
         return UUID.nameUUIDFromBytes(fullStringIdentifier.getBytes()).getMostSignificantBits();
     }
 
-    private static final int STACK_SIZE_TO_PROTO_LOG_ENTRY_CALL = 12;
+    private static final int STACK_SIZE_TO_PROTO_LOG_ENTRY_CALL = 6;
 
     private String collectStackTrace() {
         StackTraceElement[] stackTrace =  Thread.currentThread().getStackTrace();
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 9112d37..284c299 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -19,14 +19,6 @@
 
 #include "com_android_internal_os_Zygote.h"
 
-#include <async_safe/log.h>
-
-// sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc
-#include <sys/mount.h>
-#include <linux/fs.h>
-#include <sys/types.h>
-#include <dirent.h>
-
 #include <algorithm>
 #include <array>
 #include <atomic>
@@ -41,19 +33,18 @@
 
 #include <android/fdsan.h>
 #include <arpa/inet.h>
+#include <dirent.h>
 #include <fcntl.h>
 #include <grp.h>
 #include <inttypes.h>
 #include <malloc.h>
 #include <mntent.h>
-#include <paths.h>
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <sys/auxv.h>
 #include <sys/capability.h>
-#include <sys/cdefs.h>
 #include <sys/eventfd.h>
+#include <sys/mount.h>
 #include <sys/personality.h>
 #include <sys/prctl.h>
 #include <sys/resource.h>
@@ -66,6 +57,7 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include <async_safe/log.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index 05c9d02..b6f2a25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.desktopmode
 
+import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.protolog.ProtoLog
 import com.android.internal.util.FrameworkStatsLog
 import com.android.wm.shell.protolog.ShellProtoLogGroup
@@ -128,7 +129,10 @@
             /* task_y */
             taskUpdate.taskY,
             /* session_id */
-            sessionId
+            sessionId,
+            taskUpdate.minimizeReason?.reason ?: UNSET_MINIMIZE_REASON,
+            taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON,
+
         )
     }
 
@@ -142,6 +146,8 @@
          * @property taskWidth width of the task in px
          * @property taskX x-coordinate of the top-left corner
          * @property taskY y-coordinate of the top-left corner
+         * @property minimizeReason the reason the task was minimized
+         * @property unminimizeEvent the reason the task was unminimized
          *
          */
         data class TaskUpdate(
@@ -151,8 +157,52 @@
             val taskWidth: Int,
             val taskX: Int,
             val taskY: Int,
+            val minimizeReason: MinimizeReason? = null,
+            val unminimizeReason: UnminimizeReason? = null,
         )
 
+        // Default value used when the task was not minimized.
+        @VisibleForTesting
+        const val UNSET_MINIMIZE_REASON =
+            FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__UNSET_MINIMIZE
+
+        /** The reason a task was minimized. */
+        enum class MinimizeReason (val reason: Int) {
+            TASK_LIMIT(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_TASK_LIMIT
+            ),
+            MINIMIZE_BUTTON( // TODO(b/356843241): use this enum value
+                FrameworkStatsLog
+                    .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_BUTTON
+            ),
+        }
+
+        // Default value used when the task was not unminimized.
+        @VisibleForTesting
+        const val UNSET_UNMINIMIZE_REASON =
+            FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNSET_UNMINIMIZE
+
+        /** The reason a task was unminimized. */
+        enum class UnminimizeReason (val reason: Int) {
+            UNKNOWN(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_UNKNOWN
+            ),
+            TASKBAR_TAP(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_TASKBAR_TAP
+            ),
+            ALT_TAB(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_ALT_TAB
+            ),
+            TASK_LAUNCH(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_TASK_LAUNCH
+            ),
+        }
+
         /**
          * Enum EnterReason mapped to the EnterReason definition in
          * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp
index 35b2f56..a231e38 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_multitasking_windowing",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
@@ -30,6 +31,46 @@
     ],
 }
 
+java_library {
+    name: "WMShellFlickerTestsSplitScreenBase",
+    srcs: [
+        ":WMShellFlickerTestsSplitScreenBase-src",
+    ],
+    static_libs: [
+        "WMShellFlickerTestsBase",
+        "wm-shell-flicker-utils",
+        "androidx.test.ext.junit",
+        "flickertestapplib",
+        "flickerlib",
+        "flickerlib-helpers",
+        "flickerlib-trace_processor_shell",
+        "platform-test-annotations",
+        "wm-flicker-common-app-helpers",
+        "wm-flicker-common-assertions",
+        "launcher-helper-lib",
+        "launcher-aosp-tapl",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerTestsSplitScreen",
+    defaults: ["WMShellFlickerTestsDefault"],
+    manifest: "AndroidManifest.xml",
+    package_name: "com.android.wm.shell.flicker.splitscreen",
+    instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
+    test_config_template: "AndroidTestTemplate.xml",
+    srcs: ["src/**/*.kt"],
+    exclude_srcs: ["src/**/benchmark/*.kt"],
+    static_libs: [
+        "WMShellFlickerTestsBase",
+        "WMShellFlickerTestsSplitScreenBase",
+    ],
+    data: ["trace_config/*"],
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin cleanup after gcl merges
+
 filegroup {
     name: "WMShellFlickerTestsSplitScreenGroup1-src",
     srcs: [
@@ -61,27 +102,6 @@
     ],
 }
 
-java_library {
-    name: "WMShellFlickerTestsSplitScreenBase",
-    srcs: [
-        ":WMShellFlickerTestsSplitScreenBase-src",
-    ],
-    static_libs: [
-        "WMShellFlickerTestsBase",
-        "wm-shell-flicker-utils",
-        "androidx.test.ext.junit",
-        "flickertestapplib",
-        "flickerlib",
-        "flickerlib-helpers",
-        "flickerlib-trace_processor_shell",
-        "platform-test-annotations",
-        "wm-flicker-common-app-helpers",
-        "wm-flicker-common-assertions",
-        "launcher-helper-lib",
-        "launcher-aosp-tapl",
-    ],
-}
-
 android_test {
     name: "WMShellFlickerTestsSplitScreenGroup1",
     defaults: ["WMShellFlickerTestsDefault"],
@@ -154,3 +174,156 @@
     ],
     data: ["trace_config/*"],
 }
+
+////////////////////////////////////////////////////////////////////////////////
+// End cleanup after gcl merges
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for FlickerTestsRotation module
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-CatchAll",
+    base: "WMShellFlickerTestsSplitScreen",
+    exclude_filters: [
+        "com.android.wm.shell.flicker.splitscreen.CopyContentInSplit",
+        "com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByDivider",
+        "com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByGoHome",
+        "com.android.wm.shell.flicker.splitscreen.DragDividerToResize",
+        "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromAllApps",
+        "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromNotification",
+        "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromShortcut",
+        "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromTaskbar",
+        "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenFromOverview",
+        "com.android.wm.shell.flicker.splitscreen.MultipleShowImeRequestsInSplitScreen",
+        "com.android.wm.shell.flicker.splitscreen.SwitchAppByDoubleTapDivider",
+        "com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromAnotherApp",
+        "com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromHome",
+        "com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromRecent",
+        "com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairs",
+        "com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairsNoPip",
+        "com.android.wm.shell.flicker.splitscreen.",
+    ],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-CopyContentInSplit",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.CopyContentInSplit"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-DismissSplitScreenByDivider",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByDivider"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-DismissSplitScreenByGoHome",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByGoHome"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-DragDividerToResize",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.DragDividerToResize"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromAllApps",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromAllApps"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromNotification",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromNotification"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromShortcut",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromShortcut"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromTaskbar",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromTaskbar"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenFromOverview",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenFromOverview"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-MultipleShowImeRequestsInSplitScreen",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.MultipleShowImeRequestsInSplitScreen"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-SwitchAppByDoubleTapDivider",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchAppByDoubleTapDivider"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-SwitchBackToSplitFromAnotherApp",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromAnotherApp"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-SwitchBackToSplitFromHome",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromHome"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-SwitchBackToSplitFromRecent",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromRecent"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-SwitchBetweenSplitPairs",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairs"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-SwitchBetweenSplitPairsNoPip",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairsNoPip"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-UnlockKeyguardToSplitScreen",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.UnlockKeyguardToSplitScreen"],
+    test_suites: ["device-tests"],
+}
+
+// End breakdowns for FlickerTestsRotation module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
index e151ab2..29a9f10 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_app_compat",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
@@ -23,6 +24,9 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// Begin cleanup after gcl merge
+
 filegroup {
     name: "WMShellFlickerTestsAppCompat-src",
     srcs: [
@@ -41,3 +45,80 @@
     static_libs: ["WMShellFlickerTestsBase"],
     data: ["trace_config/*"],
 }
+
+////////////////////////////////////////////////////////////////////////////////
+// End cleanup after gcl merge
+
+android_test {
+    name: "WMShellFlickerTestsAppCompat",
+    defaults: ["WMShellFlickerTestsDefault"],
+    manifest: "AndroidManifest.xml",
+    package_name: "com.android.wm.shell.flicker",
+    instrumentation_target_package: "com.android.wm.shell.flicker",
+    test_config_template: "AndroidTestTemplate.xml",
+    srcs: ["src/**/*.kt"],
+    static_libs: ["WMShellFlickerTestsBase"],
+    data: ["trace_config/*"],
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for WMShellFlickerTestsAppCompat module
+
+test_module_config {
+    name: "WMShellFlickerTestsAppCompat-CatchAll",
+    base: "WMShellFlickerTestsAppCompat",
+    exclude_filters: [
+        "com.android.wm.shell.flicker.appcompat.OpenAppInSizeCompatModeTest",
+        "com.android.wm.shell.flicker.appcompat.OpenTransparentActivityTest",
+        "com.android.wm.shell.flicker.appcompat.QuickSwitchLauncherToLetterboxAppTest",
+        "com.android.wm.shell.flicker.appcompat.RepositionFixedPortraitAppTest",
+        "com.android.wm.shell.flicker.appcompat.RestartAppInSizeCompatModeTest",
+        "com.android.wm.shell.flicker.appcompat.RotateImmersiveAppInFullscreenTest",
+    ],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsAppCompat-OpenAppInSizeCompatModeTest",
+    base: "WMShellFlickerTestsAppCompat",
+    include_filters: ["com.android.wm.shell.flicker.appcompat.OpenAppInSizeCompatModeTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsAppCompat-OpenTransparentActivityTest",
+    base: "WMShellFlickerTestsAppCompat",
+    include_filters: ["com.android.wm.shell.flicker.appcompat.OpenTransparentActivityTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsAppCompat-QuickSwitchLauncherToLetterboxAppTest",
+    base: "WMShellFlickerTestsAppCompat",
+    include_filters: ["com.android.wm.shell.flicker.appcompat.QuickSwitchLauncherToLetterboxAppTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsAppCompat-RepositionFixedPortraitAppTest",
+    base: "WMShellFlickerTestsAppCompat",
+    include_filters: ["com.android.wm.shell.flicker.appcompat.RepositionFixedPortraitAppTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsAppCompat-RestartAppInSizeCompatModeTest",
+    base: "WMShellFlickerTestsAppCompat",
+    include_filters: ["com.android.wm.shell.flicker.appcompat.RestartAppInSizeCompatModeTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsAppCompat-RotateImmersiveAppInFullscreenTest",
+    base: "WMShellFlickerTestsAppCompat",
+    include_filters: ["com.android.wm.shell.flicker.appcompat.RotateImmersiveAppInFullscreenTest"],
+    test_suites: ["device-tests"],
+}
+
+// End breakdowns for FlickerTestsRotation module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp b/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp
index f0b4f1f..2ff7ab2 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_multitasking_windowing",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
@@ -34,3 +35,57 @@
     static_libs: ["WMShellFlickerTestsBase"],
     data: ["trace_config/*"],
 }
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for WMShellFlickerTestsBubbles module
+
+test_module_config {
+    name: "WMShellFlickerTestsBubbles-CatchAll",
+    base: "WMShellFlickerTestsBubbles",
+    exclude_filters: [
+        "com.android.wm.shell.flicker.bubble.ChangeActiveActivityFromBubbleTest",
+        "com.android.wm.shell.flicker.bubble.DragToDismissBubbleScreenTest",
+        "com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleOnLocksreenTest",
+        "com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleTest",
+        "com.android.wm.shell.flicker.bubble.SendBubbleNotificationTest",
+    ],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsBubbles-ChangeActiveActivityFromBubbleTest",
+    base: "WMShellFlickerTestsBubbles",
+    include_filters: ["com.android.wm.shell.flicker.bubble.ChangeActiveActivityFromBubbleTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsBubbles-DragToDismissBubbleScreenTest",
+    base: "WMShellFlickerTestsBubbles",
+    include_filters: ["com.android.wm.shell.flicker.bubble.DragToDismissBubbleScreenTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsBubbles-OpenActivityFromBubbleOnLocksreenTest",
+    base: "WMShellFlickerTestsBubbles",
+    include_filters: ["com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleOnLocksreenTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsBubbles-OpenActivityFromBubbleTest",
+    base: "WMShellFlickerTestsBubbles",
+    include_filters: ["com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsBubbles-SendBubbleNotificationTest",
+    base: "WMShellFlickerTestsBubbles",
+    include_filters: ["com.android.wm.shell.flicker.bubble.SendBubbleNotificationTest"],
+    test_suites: ["device-tests"],
+}
+
+// End breakdowns for WMShellFlickerTestsBubbles module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
index faeb342..4165ed0 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_multitasking_windowing",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
@@ -24,6 +25,14 @@
 }
 
 filegroup {
+    name: "WMShellFlickerTestsPipApps-src",
+    srcs: ["src/**/apps/*.kt"],
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin cleanup after gcl merges
+
+filegroup {
     name: "WMShellFlickerTestsPip1-src",
     srcs: [
         "src/**/A*.kt",
@@ -52,11 +61,6 @@
     srcs: ["src/**/common/*.kt"],
 }
 
-filegroup {
-    name: "WMShellFlickerTestsPipApps-src",
-    srcs: ["src/**/apps/*.kt"],
-}
-
 android_test {
     name: "WMShellFlickerTestsPip1",
     defaults: ["WMShellFlickerTestsDefault"],
@@ -107,6 +111,21 @@
     data: ["trace_config/*"],
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// End cleanup after gcl merges
+
+android_test {
+    name: "WMShellFlickerTestsPip",
+    defaults: ["WMShellFlickerTestsDefault"],
+    manifest: "AndroidManifest.xml",
+    package_name: "com.android.wm.shell.flicker.pip",
+    instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+    test_config_template: "AndroidTestTemplate.xml",
+    srcs: ["src/**/*.kt"],
+    static_libs: ["WMShellFlickerTestsBase"],
+    data: ["trace_config/*"],
+}
+
 android_test {
     name: "WMShellFlickerTestsPipApps",
     defaults: ["WMShellFlickerTestsDefault"],
@@ -146,3 +165,185 @@
     test_plan_include: "csuitePlan.xml",
     test_config_template: "csuiteDefaultTemplate.xml",
 }
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for WMShellFlickerTestsPip module
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-CatchAll",
+    base: "WMShellFlickerTestsPip",
+    exclude_filters: [
+        "com.android.wm.shell.flicker.pip.AutoEnterPipOnGoToHomeTest",
+        "com.android.wm.shell.flicker.pip.AutoEnterPipWithSourceRectHintTest",
+        "com.android.wm.shell.flicker.pip.ClosePipBySwipingDownTest",
+        "com.android.wm.shell.flicker.pip.ClosePipWithDismissButtonTest",
+        "com.android.wm.shell.flicker.pip.EnterPipOnUserLeaveHintTest",
+        "com.android.wm.shell.flicker.pip.EnterPipViaAppUiButtonTest",
+        "com.android.wm.shell.flicker.pip.ExitPipToAppViaExpandButtonTest",
+        "com.android.wm.shell.flicker.pip.ExitPipToAppViaIntentTest",
+        "com.android.wm.shell.flicker.pip.ExpandPipOnDoubleClickTest",
+        "com.android.wm.shell.flicker.pip.ExpandPipOnPinchOpenTest",
+        "com.android.wm.shell.flicker.pip.FromSplitScreenAutoEnterPipOnGoToHomeTest",
+        "com.android.wm.shell.flicker.pip.FromSplitScreenEnterPipOnUserLeaveHintTest",
+        "com.android.wm.shell.flicker.pip.MovePipDownOnShelfHeightChange",
+        "com.android.wm.shell.flicker.pip.MovePipOnImeVisibilityChangeTest",
+        "com.android.wm.shell.flicker.pip.MovePipUpOnShelfHeightChangeTest",
+        "com.android.wm.shell.flicker.pip.PipAspectRatioChangeTest",
+        "com.android.wm.shell.flicker.pip.PipDragTest",
+        "com.android.wm.shell.flicker.pip.PipDragThenSnapTest",
+        "com.android.wm.shell.flicker.pip.PipPinchInTest",
+        "com.android.wm.shell.flicker.pip.SetRequestedOrientationWhilePinned",
+        "com.android.wm.shell.flicker.pip.ShowPipAndRotateDisplay",
+    ],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-AutoEnterPipOnGoToHomeTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.AutoEnterPipOnGoToHomeTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-AutoEnterPipWithSourceRectHintTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.AutoEnterPipWithSourceRectHintTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-ClosePipBySwipingDownTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.ClosePipBySwipingDownTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-ClosePipWithDismissButtonTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.ClosePipWithDismissButtonTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-EnterPipOnUserLeaveHintTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.EnterPipOnUserLeaveHintTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-EnterPipViaAppUiButtonTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.EnterPipViaAppUiButtonTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-ExitPipToAppViaExpandButtonTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.ExitPipToAppViaExpandButtonTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-ExitPipToAppViaIntentTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.ExitPipToAppViaIntentTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-ExpandPipOnDoubleClickTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.ExpandPipOnDoubleClickTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-ExpandPipOnPinchOpenTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.ExpandPipOnPinchOpenTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-FromSplitScreenAutoEnterPipOnGoToHomeTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.FromSplitScreenAutoEnterPipOnGoToHomeTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-FromSplitScreenEnterPipOnUserLeaveHintTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.FromSplitScreenEnterPipOnUserLeaveHintTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-MovePipDownOnShelfHeightChange",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.MovePipDownOnShelfHeightChange"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-MovePipOnImeVisibilityChangeTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.MovePipOnImeVisibilityChangeTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-MovePipUpOnShelfHeightChangeTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.MovePipUpOnShelfHeightChangeTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-PipAspectRatioChangeTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.PipAspectRatioChangeTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-PipDragTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.PipDragTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-PipDragThenSnapTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.PipDragThenSnapTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-PipPinchInTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.PipPinchInTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-SetRequestedOrientationWhilePinned",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.SetRequestedOrientationWhilePinned"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-ShowPipAndRotateDisplay",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.ShowPipAndRotateDisplay"],
+    test_suites: ["device-tests"],
+}
+
+// End breakdowns for WMShellFlickerTestsPip module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index 4548fcb..70b3661 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -16,14 +16,17 @@
 
 package com.android.wm.shell.desktopmode
 
-import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
 import com.android.internal.util.FrameworkStatsLog
 import com.android.modules.utils.testing.ExtendedMockitoRule
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_MINIMIZE_REASON
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON
 import kotlinx.coroutines.runBlocking
-import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.mockito.kotlin.eq
@@ -33,7 +36,7 @@
  */
 class DesktopModeEventLoggerTest {
 
-    private val desktopModeEventLogger  = DesktopModeEventLogger()
+    private val desktopModeEventLogger = DesktopModeEventLogger()
 
     @JvmField
     @Rule
@@ -44,7 +47,7 @@
     fun logSessionEnter_enterReason() = runBlocking {
         desktopModeEventLogger.logSessionEnter(sessionId = SESSION_ID, EnterReason.UNKNOWN_ENTER)
 
-        ExtendedMockito.verify {
+        verify {
             FrameworkStatsLog.write(
                 eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
                 /* event */
@@ -63,7 +66,7 @@
     fun logSessionExit_exitReason() = runBlocking {
         desktopModeEventLogger.logSessionExit(sessionId = SESSION_ID, ExitReason.UNKNOWN_EXIT)
 
-        ExtendedMockito.verify {
+        verify {
             FrameworkStatsLog.write(
                 eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
                 /* event */
@@ -82,7 +85,7 @@
     fun logTaskAdded_taskUpdate() = runBlocking {
         desktopModeEventLogger.logTaskAdded(sessionId = SESSION_ID, TASK_UPDATE)
 
-        ExtendedMockito.verify {
+        verify {
             FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
                 /* task_event */
                 eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED),
@@ -99,7 +102,9 @@
                 /* task_y */
                 eq(TASK_UPDATE.taskY),
                 /* session_id */
-                eq(SESSION_ID))
+                eq(SESSION_ID),
+                eq(UNSET_MINIMIZE_REASON),
+                eq(UNSET_UNMINIMIZE_REASON))
         }
     }
 
@@ -107,7 +112,7 @@
     fun logTaskRemoved_taskUpdate() = runBlocking {
         desktopModeEventLogger.logTaskRemoved(sessionId = SESSION_ID, TASK_UPDATE)
 
-        ExtendedMockito.verify {
+        verify {
             FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
                 /* task_event */
                 eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED),
@@ -124,7 +129,9 @@
                 /* task_y */
                 eq(TASK_UPDATE.taskY),
                 /* session_id */
-                eq(SESSION_ID))
+                eq(SESSION_ID),
+                eq(UNSET_MINIMIZE_REASON),
+                eq(UNSET_UNMINIMIZE_REASON))
         }
     }
 
@@ -132,10 +139,11 @@
     fun logTaskInfoChanged_taskUpdate() = runBlocking {
         desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID, TASK_UPDATE)
 
-        ExtendedMockito.verify {
+        verify {
             FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
                 /* task_event */
-                eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+                eq(FrameworkStatsLog
+                    .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
                 /* instance_id */
                 eq(TASK_UPDATE.instanceId),
                 /* uid */
@@ -149,7 +157,71 @@
                 /* task_y */
                 eq(TASK_UPDATE.taskY),
                 /* session_id */
-                eq(SESSION_ID))
+                eq(SESSION_ID),
+                eq(UNSET_MINIMIZE_REASON),
+                eq(UNSET_UNMINIMIZE_REASON))
+        }
+    }
+
+    @Test
+    fun logTaskInfoChanged_logsTaskUpdateWithMinimizeReason() = runBlocking {
+        desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID,
+            createTaskUpdate(minimizeReason = MinimizeReason.TASK_LIMIT))
+
+        verify {
+            FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+                /* task_event */
+                eq(FrameworkStatsLog
+                    .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+                /* instance_id */
+                eq(TASK_UPDATE.instanceId),
+                /* uid */
+                eq(TASK_UPDATE.uid),
+                /* task_height */
+                eq(TASK_UPDATE.taskHeight),
+                /* task_width */
+                eq(TASK_UPDATE.taskWidth),
+                /* task_x */
+                eq(TASK_UPDATE.taskX),
+                /* task_y */
+                eq(TASK_UPDATE.taskY),
+                /* session_id */
+                eq(SESSION_ID),
+                /* minimize_reason */
+                eq(MinimizeReason.TASK_LIMIT.reason),
+                /* unminimize_reason */
+                eq(UNSET_UNMINIMIZE_REASON))
+        }
+    }
+
+    @Test
+    fun logTaskInfoChanged_logsTaskUpdateWithUnminimizeReason() = runBlocking {
+        desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID,
+            createTaskUpdate(unminimizeReason = UnminimizeReason.TASKBAR_TAP))
+
+        verify {
+            FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+                /* task_event */
+                eq(FrameworkStatsLog
+                    .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+                /* instance_id */
+                eq(TASK_UPDATE.instanceId),
+                /* uid */
+                eq(TASK_UPDATE.uid),
+                /* task_height */
+                eq(TASK_UPDATE.taskHeight),
+                /* task_width */
+                eq(TASK_UPDATE.taskWidth),
+                /* task_x */
+                eq(TASK_UPDATE.taskX),
+                /* task_y */
+                eq(TASK_UPDATE.taskY),
+                /* session_id */
+                eq(SESSION_ID),
+                /* minimize_reason */
+                eq(UNSET_MINIMIZE_REASON),
+                /* unminimize_reason */
+                eq(UnminimizeReason.TASKBAR_TAP.reason))
         }
     }
 
@@ -165,5 +237,11 @@
         private val TASK_UPDATE = TaskUpdate(
             TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y
         )
+
+        private fun createTaskUpdate(
+            minimizeReason: MinimizeReason? = null,
+            unminimizeReason: UnminimizeReason? = null,
+        ) = TaskUpdate(TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, minimizeReason,
+            unminimizeReason)
     }
 }
\ No newline at end of file
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index a1f5168..faea6d4 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -2,6 +2,13 @@
 container: "system"
 
 flag {
+  name: "runtime_color_filters_blenders"
+  namespace: "core_graphics"
+  description: "API for AGSL authored runtime color filters and blenders"
+  bug: "358126864"
+}
+
+flag {
   name: "clip_shader"
   is_exported: true
   namespace: "core_graphics"
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 25c767a..c36eda9 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -84,6 +84,7 @@
 import android.util.IntArray;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Slog;
 import android.view.KeyEvent;
 
 import com.android.internal.annotations.GuardedBy;
@@ -4283,7 +4284,7 @@
                                     final OnAudioFocusChangeListener listener =
                                             fri.mRequest.getOnAudioFocusChangeListener();
                                     if (listener != null) {
-                                        Log.d(TAG, "dispatching onAudioFocusChange("
+                                        Slog.i(TAG, "dispatching onAudioFocusChange("
                                                 + msg.arg1 + ") to " + msg.obj);
                                         listener.onAudioFocusChange(msg.arg1);
                                     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index de60fdc2..b3ac54a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -21,6 +21,8 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.net.Uri;
 import android.provider.DeviceConfig;
@@ -41,9 +43,12 @@
 import com.android.settingslib.widget.AdaptiveIcon;
 import com.android.settingslib.widget.AdaptiveOutlineDrawable;
 
+import com.google.common.collect.ImmutableSet;
+
 import java.io.IOException;
 import java.util.List;
 import java.util.Locale;
+import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -57,6 +62,9 @@
     public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled";
     private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
     private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
+    private static final Set<Integer> SA_PROFILES =
+            ImmutableSet.of(
+                    BluetoothProfile.A2DP, BluetoothProfile.LE_AUDIO, BluetoothProfile.HEARING_AID);
 
     private static ErrorListener sErrorListener;
 
@@ -895,4 +903,62 @@
         }
         return null;
     }
+
+    /**
+     * Gets {@link AudioDeviceAttributes} of bluetooth device for spatial audio. Returns null if
+     * it's not an audio device(no A2DP, LE Audio and Hearing Aid profile).
+     */
+    @Nullable
+    public static AudioDeviceAttributes getAudioDeviceAttributesForSpatialAudio(
+            CachedBluetoothDevice cachedDevice,
+            @AudioManager.AudioDeviceCategory int audioDeviceCategory) {
+        AudioDeviceAttributes saDevice = null;
+        for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) {
+            // pick first enabled profile that is compatible with spatial audio
+            if (SA_PROFILES.contains(profile.getProfileId())
+                    && profile.isEnabled(cachedDevice.getDevice())) {
+                switch (profile.getProfileId()) {
+                    case BluetoothProfile.A2DP:
+                        saDevice =
+                                new AudioDeviceAttributes(
+                                        AudioDeviceAttributes.ROLE_OUTPUT,
+                                        AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+                                        cachedDevice.getAddress());
+                        break;
+                    case BluetoothProfile.LE_AUDIO:
+                        if (audioDeviceCategory
+                                == AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER) {
+                            saDevice =
+                                    new AudioDeviceAttributes(
+                                            AudioDeviceAttributes.ROLE_OUTPUT,
+                                            AudioDeviceInfo.TYPE_BLE_SPEAKER,
+                                            cachedDevice.getAddress());
+                        } else {
+                            saDevice =
+                                    new AudioDeviceAttributes(
+                                            AudioDeviceAttributes.ROLE_OUTPUT,
+                                            AudioDeviceInfo.TYPE_BLE_HEADSET,
+                                            cachedDevice.getAddress());
+                        }
+
+                        break;
+                    case BluetoothProfile.HEARING_AID:
+                        saDevice =
+                                new AudioDeviceAttributes(
+                                        AudioDeviceAttributes.ROLE_OUTPUT,
+                                        AudioDeviceInfo.TYPE_HEARING_AID,
+                                        cachedDevice.getAddress());
+                        break;
+                    default:
+                        Log.i(
+                                TAG,
+                                "unrecognized profile for spatial audio: "
+                                        + profile.getProfileId());
+                        break;
+                }
+                break;
+            }
+        }
+        return saDevice;
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
index 20a0339..58dc8c7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
@@ -108,6 +108,12 @@
     /** Device setting ID for device details footer. */
     int DEVICE_SETTING_ID_DEVICE_DETAILS_FOOTER = 19;
 
+    /** Device setting ID for spatial audio group. */
+    int DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE = 20;
+
+    /** Device setting ID for "More Settings" page. */
+    int DEVICE_SETTING_ID_MORE_SETTINGS = 21;
+
     /** Device setting ID for ANC. */
     int DEVICE_SETTING_ID_ANC = 1001;
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
index a599dd1..ce7064c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
@@ -29,6 +29,7 @@
 import com.android.settingslib.bluetooth.devicesettings.ToggleInfo
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
@@ -117,7 +118,7 @@
                     id = settingId,
                     title = pref.title,
                     summary = pref.summary,
-                    icon = pref.icon,
+                    icon = pref.icon?.let { DeviceSettingIcon.BitmapIcon(it) },
                     isAllowedChangingState = pref.isAllowedChangingState,
                     intent = pref.intent,
                     switchState =
@@ -153,5 +154,6 @@
             else -> DeviceSettingModel.Unknown(cachedDevice, settingId)
         }
 
-    private fun ToggleInfo.toModel(): ToggleModel = ToggleModel(label, icon)
+    private fun ToggleInfo.toModel(): ToggleModel =
+        ToggleModel(label, DeviceSettingIcon.BitmapIcon(icon))
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
index 136abad..e97f76c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
@@ -35,7 +35,7 @@
     /** A built-in item in Settings. */
     data class BuiltinItem(
         @DeviceSettingId override val settingId: Int,
-        val preferenceKey: String
+        val preferenceKey: String?
     ) : DeviceSettingConfigItemModel
 
     /** A remote item provided by other apps. */
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt
index db78280..2a63217 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt
@@ -18,6 +18,7 @@
 
 import android.content.Intent
 import android.graphics.Bitmap
+import androidx.annotation.DrawableRes
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
 
@@ -32,7 +33,7 @@
         @DeviceSettingId override val id: Int,
         val title: String,
         val summary: String? = null,
-        val icon: Bitmap? = null,
+        val icon: DeviceSettingIcon? = null,
         val intent: Intent? = null,
         val switchState: DeviceSettingStateModel.ActionSwitchPreferenceState? = null,
         val isAllowedChangingState: Boolean = true,
@@ -59,4 +60,12 @@
 }
 
 /** Models a toggle in [DeviceSettingModel.MultiTogglePreference]. */
-data class ToggleModel(val label: String, val icon: Bitmap)
+data class ToggleModel(val label: String, val icon: DeviceSettingIcon)
+
+/** Models an icon in device settings. */
+sealed interface DeviceSettingIcon {
+
+    data class BitmapIcon(val bitmap: Bitmap) : DeviceSettingIcon
+
+    data class ResourceIcon(@DrawableRes val resId: Int) : DeviceSettingIcon
+}
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 4b141e7..69ef718 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -22,6 +22,7 @@
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleEvent;
 import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleTime;
+import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
 import static android.service.notification.ZenModeConfig.tryParseCountdownConditionId;
 import static android.service.notification.ZenModeConfig.tryParseEventConditionId;
 import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId;
@@ -129,7 +130,11 @@
     }
 
     public static ZenMode manualDndMode(AutomaticZenRule manualRule, boolean isActive) {
-        return new ZenMode(MANUAL_DND_MODE_ID, manualRule,
+        // Manual rule is owned by the system, so we set it here
+        AutomaticZenRule manualRuleWithPkg = new AutomaticZenRule.Builder(manualRule)
+                .setPackage(PACKAGE_ANDROID)
+                .build();
+        return new ZenMode(MANUAL_DND_MODE_ID, manualRuleWithPkg,
                 isActive ? Status.ENABLED_AND_ACTIVE : Status.ENABLED, true);
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 4551f1e..926d3cb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -31,10 +31,13 @@
 import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.net.Uri;
 import android.platform.test.flag.junit.SetFlagsRule;
@@ -70,6 +73,9 @@
     @Mock private BluetoothDevice mBluetoothDevice;
     @Mock private AudioManager mAudioManager;
     @Mock private PackageManager mPackageManager;
+    @Mock private LeAudioProfile mA2dpProfile;
+    @Mock private LeAudioProfile mLeAudioProfile;
+    @Mock private LeAudioProfile mHearingAid;
     @Mock private LocalBluetoothLeBroadcast mBroadcast;
     @Mock private LocalBluetoothProfileManager mProfileManager;
     @Mock private LocalBluetoothManager mLocalBluetoothManager;
@@ -100,6 +106,9 @@
         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
         when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
         when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
+        when(mA2dpProfile.getProfileId()).thenReturn(BluetoothProfile.A2DP);
+        when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO);
+        when(mHearingAid.getProfileId()).thenReturn(BluetoothProfile.HEARING_AID);
     }
 
     @Test
@@ -756,4 +765,84 @@
                                 mContext.getContentResolver(), mLocalBluetoothManager))
                 .isEqualTo(mCachedBluetoothDevice);
     }
+
+    @Test
+    public void getAudioDeviceAttributesForSpatialAudio_bleHeadset() {
+        String address = "11:22:33:44:55:66";
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+        when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mLeAudioProfile));
+        when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(true);
+
+        AudioDeviceAttributes attr =
+                BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+                        mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES);
+
+        assertThat(attr)
+                .isEqualTo(
+                        new AudioDeviceAttributes(
+                                AudioDeviceAttributes.ROLE_OUTPUT,
+                                AudioDeviceInfo.TYPE_BLE_HEADSET,
+                                address));
+    }
+
+    @Test
+    public void getAudioDeviceAttributesForSpatialAudio_bleSpeaker() {
+        String address = "11:22:33:44:55:66";
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+        when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mLeAudioProfile));
+        when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(true);
+
+        AudioDeviceAttributes attr =
+                BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+                        mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER);
+
+        assertThat(attr)
+                .isEqualTo(
+                        new AudioDeviceAttributes(
+                                AudioDeviceAttributes.ROLE_OUTPUT,
+                                AudioDeviceInfo.TYPE_BLE_SPEAKER,
+                                address));
+    }
+
+    @Test
+    public void getAudioDeviceAttributesForSpatialAudio_a2dp() {
+        String address = "11:22:33:44:55:66";
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+        when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mA2dpProfile));
+        when(mA2dpProfile.isEnabled(mBluetoothDevice)).thenReturn(true);
+
+        AudioDeviceAttributes attr =
+                BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+                        mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES);
+
+        assertThat(attr)
+                .isEqualTo(
+                        new AudioDeviceAttributes(
+                                AudioDeviceAttributes.ROLE_OUTPUT,
+                                AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+                                address));
+    }
+
+    @Test
+    public void getAudioDeviceAttributesForSpatialAudio_hearingAid() {
+        String address = "11:22:33:44:55:66";
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+        when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mHearingAid));
+        when(mHearingAid.isEnabled(mBluetoothDevice)).thenReturn(true);
+
+        AudioDeviceAttributes attr =
+                BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+                        mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_HEARING_AID);
+
+        assertThat(attr)
+                .isEqualTo(
+                        new AudioDeviceAttributes(
+                                AudioDeviceAttributes.ROLE_OUTPUT,
+                                AudioDeviceInfo.TYPE_HEARING_AID,
+                                address));
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
index fee2394..4c5ee9e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
@@ -40,6 +40,7 @@
 import com.android.settingslib.bluetooth.devicesettings.ToggleInfo
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
@@ -352,7 +353,7 @@
                 val pref = serviceResponse.preference as ActionSwitchPreference
                 assertThat(actual.title).isEqualTo(pref.title)
                 assertThat(actual.summary).isEqualTo(pref.summary)
-                assertThat(actual.icon).isEqualTo(pref.icon)
+                assertThat(actual.icon).isEqualTo(DeviceSettingIcon.BitmapIcon(pref.icon!!))
                 assertThat(actual.isAllowedChangingState).isEqualTo(pref.isAllowedChangingState)
                 if (pref.hasSwitch()) {
                     assertThat(actual.switchState!!.checked).isEqualTo(pref.checked)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
index d9fdcc38..f464247 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
@@ -19,6 +19,7 @@
 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -67,6 +68,7 @@
         assertThat(manualMode.canEditNameAndIcon()).isFalse();
         assertThat(manualMode.canBeDeleted()).isFalse();
         assertThat(manualMode.isActive()).isFalse();
+        assertThat(manualMode.getRule().getPackageName()).isEqualTo(PACKAGE_ANDROID);
     }
 
     @Test
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 6d78705..5ea75be 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -748,6 +748,7 @@
         "//frameworks/libs/systemui:motion_tool_lib",
         "//frameworks/libs/systemui:contextualeducationlib",
         "androidx.core_core-animation-testing",
+        "androidx.lifecycle_lifecycle-runtime-testing",
         "androidx.compose.ui_ui",
         "flag-junit",
         "ravenwood-junit",
@@ -789,6 +790,7 @@
         "SystemUI-tests-base",
         "androidx.test.uiautomator_uiautomator",
         "androidx.core_core-animation-testing",
+        "androidx.lifecycle_lifecycle-runtime-testing",
         "mockito-target-extended-minus-junit4",
         "mockito-kotlin-nodeps",
         "androidx.test.ext.junit",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 5632e30..df28654 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -380,6 +380,17 @@
 }
 
 flag {
+    name: "status_bar_stop_updating_window_height"
+    namespace: "systemui"
+    description: "Don't have PhoneStatusBarView manually trigger an update of the height in "
+        "StatusBarWindowController"
+    bug: "360115167"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "compose_bouncer"
     namespace: "systemui"
     description: "Use the new compose bouncer in SystemUI"
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 b93b049..7472d81 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
@@ -1410,7 +1410,10 @@
                                     R.string.accessibility_action_label_close_communal_hub
                                 )
                             ) {
-                                viewModel.changeScene(CommunalScenes.Blank)
+                                viewModel.changeScene(
+                                    CommunalScenes.Blank,
+                                    "closed by accessibility"
+                                )
                                 true
                             },
                             CustomAccessibilityAction(
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 eea00c4..fb7c422 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
@@ -29,8 +29,6 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Button
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
@@ -42,6 +40,7 @@
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.ui.composable.LockscreenContent
 import com.android.systemui.lifecycle.rememberViewModel
@@ -114,7 +113,7 @@
 }
 
 @Composable
-private fun ShadeBody(
+fun ShadeBody(
     viewModel: QuickSettingsContainerViewModel,
 ) {
     val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle()
@@ -131,6 +130,7 @@
         } else {
             QuickSettingsLayout(
                 viewModel = viewModel,
+                modifier = Modifier.sysuiResTag("quick_settings_panel")
             )
         }
     }
@@ -158,11 +158,6 @@
                 Modifier.fillMaxWidth().heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight),
             viewModel.editModeViewModel::startEditing,
         )
-        Button(
-            onClick = { viewModel.editModeViewModel.startEditing() },
-        ) {
-            Text("Edit mode")
-        }
     }
 }
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index b329534..3487730 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -81,6 +81,7 @@
     enabled: () -> Boolean,
     startDragImmediately: (startedPosition: Offset) -> Boolean,
     onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+    onFirstPointerDown: () -> Unit = {},
     swipeDetector: SwipeDetector = DefaultSwipeDetector,
     dispatcher: NestedScrollDispatcher,
 ): Modifier =
@@ -90,6 +91,7 @@
             enabled,
             startDragImmediately,
             onDragStarted,
+            onFirstPointerDown,
             swipeDetector,
             dispatcher,
         )
@@ -101,6 +103,7 @@
     private val startDragImmediately: (startedPosition: Offset) -> Boolean,
     private val onDragStarted:
         (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+    private val onFirstPointerDown: () -> Unit,
     private val swipeDetector: SwipeDetector,
     private val dispatcher: NestedScrollDispatcher,
 ) : ModifierNodeElement<MultiPointerDraggableNode>() {
@@ -110,6 +113,7 @@
             enabled = enabled,
             startDragImmediately = startDragImmediately,
             onDragStarted = onDragStarted,
+            onFirstPointerDown = onFirstPointerDown,
             swipeDetector = swipeDetector,
             dispatcher = dispatcher,
         )
@@ -119,6 +123,7 @@
         node.enabled = enabled
         node.startDragImmediately = startDragImmediately
         node.onDragStarted = onDragStarted
+        node.onFirstPointerDown = onFirstPointerDown
         node.swipeDetector = swipeDetector
     }
 }
@@ -129,6 +134,7 @@
     var startDragImmediately: (startedPosition: Offset) -> Boolean,
     var onDragStarted:
         (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+    var onFirstPointerDown: () -> Unit,
     var swipeDetector: SwipeDetector = DefaultSwipeDetector,
     private val dispatcher: NestedScrollDispatcher,
 ) :
@@ -225,6 +231,7 @@
                             startedPosition = null
                         } else if (startedPosition == null) {
                             startedPosition = pointers.first().position
+                            onFirstPointerDown()
                         }
                     }
                 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index f062146..d1e83ba 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -67,6 +67,7 @@
                 enabled = ::enabled,
                 startDragImmediately = ::startDragImmediately,
                 onDragStarted = draggableHandler::onDragStarted,
+                onFirstPointerDown = ::onFirstPointerDown,
                 swipeDetector = swipeDetector,
                 dispatcher = dispatcher,
             )
@@ -101,6 +102,15 @@
         delegate(ScrollBehaviorOwnerNode(draggableHandler.nestedScrollKey, nestedScrollHandlerImpl))
     }
 
+    private fun onFirstPointerDown() {
+        // When we drag our finger across the screen, the NestedScrollConnection keeps track of all
+        // the scroll events until we lift our finger. However, in some cases, the connection might
+        // not receive the "up" event. This can lead to an incorrect initial state for the gesture.
+        // To prevent this issue, we can call the reset() method when the first finger touches the
+        // screen. This ensures that the NestedScrollConnection starts from a correct state.
+        nestedScrollHandlerImpl.connection.reset()
+    }
+
     override fun onDetach() {
         // Make sure we reset the scroll connection when this modifier is removed from composition
         nestedScrollHandlerImpl.connection.reset()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index 228f7ba..16fb533b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -129,7 +129,11 @@
         return onPriorityStop(available)
     }
 
-    /** Method to call before destroying the object or to reset the initial state. */
+    /**
+     * Method to call before destroying the object or to reset the initial state.
+     *
+     * TODO(b/303224944) This method should be removed.
+     */
     fun reset() {
         // Step 3c: To ensure that an onStop is always called for every onStart.
         onPriorityStop(velocity = Velocity.Zero)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
index 9ebc426..d8a06f5 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
@@ -23,6 +23,9 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.platform.LocalViewConfiguration
@@ -266,4 +269,38 @@
         val transition = assertThat(state.transitionState).isTransition()
         assertThat(transition).hasProgress(0.5f)
     }
+
+    @Test
+    fun resetScrollTracking_afterMissingPointerUpEvent() {
+        var canScroll = true
+        var hasScrollable by mutableStateOf(true)
+        val state = setup2ScenesAndScrollTouchSlop {
+            if (hasScrollable) {
+                Modifier.scrollable(rememberScrollableState { if (canScroll) it else 0f }, Vertical)
+            } else {
+                Modifier
+            }
+        }
+
+        // The gesture is consumed by the component in the scene.
+        scrollUp(percent = 0.2f)
+
+        // STL keeps track of the scroll consumed. The scene remains in Idle.
+        assertThat(state.transitionState).isIdle()
+
+        // The scrollable component disappears, and does not send the signal (pointer up) to reset
+        // the consumed amount.
+        hasScrollable = false
+        pointerUp()
+
+        // A new scrollable component appears and allows the scene to consume the scroll.
+        hasScrollable = true
+        canScroll = false
+        pointerDownAndScrollTouchSlop()
+        scrollUp(percent = 0.2f)
+
+        // STL can only start the transition if it has reset the amount of scroll consumed.
+        val transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasProgress(0.2f)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 9ccf99b..70529cc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -112,7 +112,7 @@
             testScope.runTest {
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
 
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 communalSceneInteractor.setEditModeState(EditModeState.STARTING)
@@ -133,7 +133,7 @@
             testScope.runTest {
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
 
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 communalSceneInteractor.setIsLaunchingWidget(true)
@@ -154,7 +154,7 @@
             testScope.runTest {
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
 
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 communalSceneInteractor.setIsLaunchingWidget(false)
@@ -174,7 +174,7 @@
         with(kosmos) {
             testScope.runTest {
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 communalInteractor.setEditModeOpen(true)
@@ -213,7 +213,7 @@
             testScope.runTest {
                 whenever(centralSurfaces.isLaunchingActivityOverLockscreen).thenReturn(false)
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 updateDocked(true)
@@ -233,7 +233,7 @@
             testScope.runTest {
                 whenever(centralSurfaces.isLaunchingActivityOverLockscreen).thenReturn(true)
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 updateDocked(true)
@@ -270,7 +270,7 @@
         with(kosmos) {
             testScope.runTest {
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
@@ -292,7 +292,7 @@
         with(kosmos) {
             testScope.runTest {
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
@@ -320,7 +320,7 @@
     fun dockingOnLockscreen_forcesCommunal() =
         with(kosmos) {
             testScope.runTest {
-                communalSceneInteractor.changeScene(CommunalScenes.Blank)
+                communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
 
                 // device is docked while on the lockscreen
@@ -342,7 +342,7 @@
     fun dockingOnLockscreen_doesNotForceCommunalIfDreamStarts() =
         with(kosmos) {
             testScope.runTest {
-                communalSceneInteractor.changeScene(CommunalScenes.Blank)
+                communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
 
                 // device is docked while on the lockscreen
@@ -374,7 +374,7 @@
             testScope.runTest {
                 // Device is dreaming and on communal.
                 updateDreaming(true)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -391,7 +391,7 @@
             testScope.runTest {
                 // Device is not dreaming and on communal.
                 updateDreaming(false)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
                 // Scene stays as Communal
                 advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
@@ -406,7 +406,7 @@
             testScope.runTest {
                 // Device is dreaming and on communal.
                 updateDreaming(true)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -429,7 +429,7 @@
             testScope.runTest {
                 // Device is on communal, but not dreaming.
                 updateDreaming(false)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -450,7 +450,7 @@
         with(kosmos) {
             testScope.runTest {
                 // Device is on communal.
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
                 // Device stays on the hub after the timeout since we're not dreaming.
                 advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2)
@@ -471,7 +471,7 @@
             testScope.runTest {
                 // Device is dreaming and on communal.
                 updateDreaming(true)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -500,7 +500,7 @@
 
                 // Device is dreaming and on communal.
                 updateDreaming(true)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -520,7 +520,7 @@
         with(kosmos) {
             testScope.runTest {
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
-                communalSceneInteractor.changeScene(CommunalScenes.Blank)
+                communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
                 assertThat(scene).isEqualTo(CommunalScenes.Blank)
 
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index e57a4cb..864795b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -482,7 +482,7 @@
         testScope.runTest {
             val targetScene = CommunalScenes.Communal
 
-            underTest.changeScene(targetScene)
+            underTest.changeScene(targetScene, "test")
 
             val desiredScene = collectLastValue(communalRepository.currentScene)
             runCurrent()
@@ -635,7 +635,7 @@
             runCurrent()
             assertThat(isCommunalShowing()).isEqualTo(false)
 
-            underTest.changeScene(CommunalScenes.Communal)
+            underTest.changeScene(CommunalScenes.Communal, "test")
 
             isCommunalShowing = collectLastValue(underTest.isCommunalShowing)
             runCurrent()
@@ -659,12 +659,12 @@
             assertThat(isCommunalShowing).isFalse()
 
             // Verify scene changes (without the flag) to communal sets the value to true
-            underTest.changeScene(CommunalScenes.Communal)
+            underTest.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
             assertThat(isCommunalShowing).isTrue()
 
             // Verify scene changes (without the flag) to blank sets the value back to false
-            underTest.changeScene(CommunalScenes.Blank)
+            underTest.changeScene(CommunalScenes.Blank, "test")
             runCurrent()
             assertThat(isCommunalShowing).isFalse()
         }
@@ -679,7 +679,7 @@
             assertThat(isCommunalShowing).isFalse()
 
             // Verify scene changes without the flag doesn't have any impact
-            underTest.changeScene(CommunalScenes.Communal)
+            underTest.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
             assertThat(isCommunalShowing).isFalse()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
index 43293c7..ed7e910 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
@@ -53,7 +53,7 @@
             val currentScene by collectLastValue(underTest.currentScene)
             assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
 
-            underTest.changeScene(CommunalScenes.Communal)
+            underTest.changeScene(CommunalScenes.Communal, "test")
             assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
         }
 
@@ -63,7 +63,7 @@
             val currentScene by collectLastValue(underTest.currentScene)
             assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
 
-            underTest.snapToScene(CommunalScenes.Communal)
+            underTest.snapToScene(CommunalScenes.Communal, "test")
             assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
         }
 
@@ -75,6 +75,7 @@
             assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
             underTest.snapToScene(
                 CommunalScenes.Communal,
+                "test",
                 ActivityTransitionAnimator.TIMINGS.totalDuration
             )
             assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
@@ -86,7 +87,7 @@
     fun changeSceneForActivityStartOnDismissKeyguard() =
         testScope.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
-            underTest.snapToScene(CommunalScenes.Communal)
+            underTest.snapToScene(CommunalScenes.Communal, "test")
             assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
 
             underTest.changeSceneForActivityStartOnDismissKeyguard()
@@ -97,7 +98,7 @@
     fun changeSceneForActivityStartOnDismissKeyguard_willNotChangeScene_forEditModeActivity() =
         testScope.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
-            underTest.snapToScene(CommunalScenes.Communal)
+            underTest.snapToScene(CommunalScenes.Communal, "test")
             assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
 
             underTest.setEditModeState(EditModeState.STARTING)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
index 3a23e14..7e28e19 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -158,7 +158,7 @@
             kosmos.setCommunalAvailable(true)
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
 
-            communalInteractor.changeScene(CommunalScenes.Blank)
+            communalInteractor.changeScene(CommunalScenes.Blank, "test")
 
             assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_NOT_STARTED)
         }
@@ -171,7 +171,7 @@
             goToCommunal()
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
 
-            communalInteractor.changeScene(CommunalScenes.Blank)
+            communalInteractor.changeScene(CommunalScenes.Blank, "test")
 
             assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
         }
@@ -184,13 +184,13 @@
             goToCommunal()
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
-            communalInteractor.changeScene(CommunalScenes.Blank)
+            communalInteractor.changeScene(CommunalScenes.Blank, "test")
 
             assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
         }
 
     private suspend fun goToCommunal() {
         kosmos.setCommunalAvailable(true)
-        communalInteractor.changeScene(CommunalScenes.Communal)
+        communalInteractor.changeScene(CommunalScenes.Communal, "test")
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
index e36fd75..a052b07 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
@@ -76,7 +76,7 @@
                 val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
 
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
                 communalSceneInteractor.setIsLaunchingWidget(true)
                 assertTrue(launching!!)
@@ -103,7 +103,7 @@
                 val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
 
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
                 communalSceneInteractor.setIsLaunchingWidget(true)
                 assertTrue(launching!!)
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 7a86e57..da82b5f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -68,7 +68,6 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.touch.TouchInsetManager
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -87,6 +86,7 @@
 import org.mockito.Mockito.isNull
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.spy
@@ -669,7 +669,7 @@
             runCurrent()
             verify(mDreamOverlayCallback).onRedirectWake(true)
             client.onWakeRequested()
-            verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), isNull())
+            verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), any(), isNull())
             verify(mUiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START)
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 8c1e8de..9792c28 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -845,7 +845,7 @@
             runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
 
             // WHEN the glanceable hub is shown
-            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
 
             assertThat(transitionRepository)
@@ -1004,7 +1004,7 @@
     fun alternateBouncerToGlanceableHub() =
         testScope.runTest {
             // GIVEN the device is idle on the glanceable hub
-            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
             clearInvocations(transitionRepository)
 
@@ -1123,7 +1123,7 @@
     fun primaryBouncerToGlanceableHub() =
         testScope.runTest {
             // GIVEN the device is idle on the glanceable hub
-            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
 
             // GIVEN a prior transition has run to PRIMARY_BOUNCER
@@ -1157,7 +1157,7 @@
             advanceTimeBy(600L)
 
             // GIVEN the device is idle on the glanceable hub
-            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
 
             // GIVEN a prior transition has run to PRIMARY_BOUNCER
@@ -1971,7 +1971,7 @@
     fun glanceableHubToLockscreen_communalKtfRefactor() =
         testScope.runTest {
             // GIVEN a prior transition has run to GLANCEABLE_HUB
-            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
             clearInvocations(transitionRepository)
 
@@ -2035,7 +2035,7 @@
     fun glanceableHubToDozing_communalKtfRefactor() =
         testScope.runTest {
             // GIVEN a prior transition has run to GLANCEABLE_HUB
-            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
             clearInvocations(transitionRepository)
 
@@ -2136,7 +2136,7 @@
     fun glanceableHubToOccluded_communalKtfRefactor() =
         testScope.runTest {
             // GIVEN a prior transition has run to GLANCEABLE_HUB
-            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
             clearInvocations(transitionRepository)
 
@@ -2184,7 +2184,7 @@
     fun glanceableHubToGone_communalKtfRefactor() =
         testScope.runTest {
             // GIVEN a prior transition has run to GLANCEABLE_HUB
-            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
             clearInvocations(transitionRepository)
 
@@ -2265,7 +2265,7 @@
             advanceTimeBy(600L)
 
             // GIVEN a prior transition has run to GLANCEABLE_HUB
-            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
             clearInvocations(transitionRepository)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
new file mode 100644
index 0000000..5999265
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -0,0 +1,151 @@
+/*
+ * 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.composefragment.viewmodel
+
+import android.content.testableContext
+import android.testing.TestableLooper.RunWithLooper
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.fgsManagerController
+import com.android.systemui.res.R
+import com.android.systemui.shade.largeScreenHeaderHelper
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestResult
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
+class QSFragmentComposeViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
+    private val lifecycleOwner =
+        TestLifecycleOwner(
+            initialState = Lifecycle.State.CREATED,
+            coroutineDispatcher = kosmos.testDispatcher,
+        )
+
+    private val underTest by lazy {
+        kosmos.qsFragmentComposeViewModelFactory.create(lifecycleOwner.lifecycleScope)
+    }
+
+    @Before
+    fun setUp() {
+        Dispatchers.setMain(kosmos.testDispatcher)
+    }
+
+    @After
+    fun teardown() {
+        Dispatchers.resetMain()
+    }
+
+    // For now the state changes at 0.5f expansion. This will change once we implement animation
+    // (and this test will fail)
+    @Test
+    fun qsExpansionValueChanges_correctExpansionState() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                val expansionState by collectLastValue(underTest.expansionState)
+
+                underTest.qsExpansionValue = 0f
+                assertThat(expansionState)
+                    .isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QQS)
+
+                underTest.qsExpansionValue = 0.3f
+                assertThat(expansionState)
+                    .isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QQS)
+
+                underTest.qsExpansionValue = 0.7f
+                assertThat(expansionState).isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QS)
+
+                underTest.qsExpansionValue = 1f
+                assertThat(expansionState).isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QS)
+            }
+        }
+
+    @Test
+    fun qqsHeaderHeight_largeScreenHeader_0() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                val qqsHeaderHeight by collectLastValue(underTest.qqsHeaderHeight)
+
+                testableContext.orCreateTestableResources.addOverride(
+                    R.bool.config_use_large_screen_shade_header,
+                    true
+                )
+                fakeConfigurationRepository.onConfigurationChange()
+
+                assertThat(qqsHeaderHeight).isEqualTo(0)
+            }
+        }
+
+    @Test
+    fun qqsHeaderHeight_noLargeScreenHeader_providedByHelper() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                val qqsHeaderHeight by collectLastValue(underTest.qqsHeaderHeight)
+
+                testableContext.orCreateTestableResources.addOverride(
+                    R.bool.config_use_large_screen_shade_header,
+                    false
+                )
+                fakeConfigurationRepository.onConfigurationChange()
+
+                assertThat(qqsHeaderHeight)
+                    .isEqualTo(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+            }
+        }
+
+    @Test
+    fun footerActionsControllerInit() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                underTest
+                runCurrent()
+                assertThat(fgsManagerController.initialized).isTrue()
+            }
+        }
+
+    private inline fun TestScope.testWithinLifecycle(
+        crossinline block: suspend TestScope.() -> TestResult
+    ): TestResult {
+        return runTest {
+            lifecycleOwner.setCurrentState(Lifecycle.State.RESUMED)
+            block().also { lifecycleOwner.setCurrentState(Lifecycle.State.DESTROYED) }
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
index 9563538..1118a61 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.ui.viewmodel
 
 import android.testing.TestableLooper.RunWithLooper
+import androidx.lifecycle.LifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -59,7 +60,7 @@
     private val footerActionsViewModel = mock<FooterActionsViewModel>()
     private val footerActionsViewModelFactory =
         mock<FooterActionsViewModel.Factory> {
-            whenever(create(any())).thenReturn(footerActionsViewModel)
+            whenever(create(any<LifecycleOwner>())).thenReturn(footerActionsViewModel)
         }
     private val footerActionsController = mock<FooterActionsController>()
 
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 bcad7e7..54b7d25 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
@@ -23,6 +23,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.settingslib.notification.modes.ZenMode
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testDispatcher
@@ -30,6 +31,7 @@
 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.statusbar.policy.ui.dialog.mockModesDialogEventLogger
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -40,6 +42,7 @@
 import org.junit.runner.RunWith
 import org.mockito.Mockito.clearInvocations
 import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
 
 @SmallTest
@@ -50,9 +53,16 @@
     private val repository = kosmos.fakeZenModeRepository
     private val interactor = kosmos.zenModeInteractor
     private val mockDialogDelegate = kosmos.mockModesDialogDelegate
+    private val mockDialogEventLogger = kosmos.mockModesDialogEventLogger
 
     private val underTest =
-        ModesDialogViewModel(context, interactor, kosmos.testDispatcher, mockDialogDelegate)
+        ModesDialogViewModel(
+            context,
+            interactor,
+            kosmos.testDispatcher,
+            mockDialogDelegate,
+            mockDialogEventLogger
+        )
 
     @Test
     fun tiles_filtersOutUserDisabledModes() =
@@ -432,4 +442,84 @@
             assertThat(intent.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID))
                 .isEqualTo("B")
         }
+
+    @Test
+    fun onClick_logsOnOffEvents() =
+        testScope.runTest {
+            val tiles by collectLastValue(underTest.tiles)
+
+            repository.addModes(
+                listOf(
+                    TestModeBuilder.MANUAL_DND_ACTIVE,
+                    TestModeBuilder()
+                        .setId("id1")
+                        .setName("Inactive Mode One")
+                        .setActive(false)
+                        .setManualInvocationAllowed(true)
+                        .build(),
+                    TestModeBuilder()
+                        .setId("id2")
+                        .setName("Active Non-Invokable Mode Two") // but can be turned off by tile
+                        .setActive(true)
+                        .setManualInvocationAllowed(false)
+                        .build(),
+                )
+            )
+            runCurrent()
+
+            assertThat(tiles?.size).isEqualTo(3)
+
+            // Trigger onClick for each tile in sequence
+            tiles?.forEach { it.onClick.invoke() }
+            runCurrent()
+
+            val onModeCaptor = argumentCaptor<ZenMode>()
+            val offModeCaptor = argumentCaptor<ZenMode>()
+
+            // manual mode and mode 2 should have turned off
+            verify(mockDialogEventLogger, times(2)).logModeOff(offModeCaptor.capture())
+            val off0 = offModeCaptor.firstValue
+            assertThat(off0.isManualDnd).isTrue()
+
+            val off1 = offModeCaptor.secondValue
+            assertThat(off1.id).isEqualTo("id2")
+
+            // should also have logged turning mode 1 on
+            verify(mockDialogEventLogger).logModeOn(onModeCaptor.capture())
+            val on = onModeCaptor.lastValue
+            assertThat(on.id).isEqualTo("id1")
+        }
+
+    @Test
+    fun onLongClick_logsSettingsEvents() =
+        testScope.runTest {
+            val tiles by collectLastValue(underTest.tiles)
+
+            repository.addModes(
+                listOf(
+                    TestModeBuilder.MANUAL_DND_ACTIVE,
+                    TestModeBuilder()
+                        .setId("id1")
+                        .setName("Inactive Mode One")
+                        .setActive(false)
+                        .setManualInvocationAllowed(true)
+                        .build(),
+                )
+            )
+            runCurrent()
+
+            assertThat(tiles?.size).isEqualTo(2)
+            val modeCaptor = argumentCaptor<ZenMode>()
+
+            // long click manual DND and then automatic mode
+            tiles?.forEach { it.onLongClick.invoke() }
+            runCurrent()
+
+            verify(mockDialogEventLogger, times(2)).logModeSettings(modeCaptor.capture())
+            val manualMode = modeCaptor.firstValue
+            assertThat(manualMode.isManualDnd).isTrue()
+
+            val automaticMode = modeCaptor.lastValue
+            assertThat(automaticMode.id).isEqualTo("id1")
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
index 7385a47..7c55f7a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
@@ -32,7 +32,6 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.volume.data.repository.TestAudioDevicesFactory
 import com.android.systemui.volume.data.repository.audioRepository
-import com.android.systemui.volume.data.repository.audioSharingRepository
 import com.android.systemui.volume.domain.model.AudioOutputDevice
 import com.android.systemui.volume.localMediaController
 import com.android.systemui.volume.localMediaRepository
@@ -222,32 +221,4 @@
 
         val testIcon = TestStubDrawable()
     }
-
-    @Test
-    fun inAudioSharing_returnTrue() {
-        with(kosmos) {
-            testScope.runTest {
-                audioSharingRepository.setInAudioSharing(true)
-
-                val inAudioSharing by collectLastValue(underTest.isInAudioSharing)
-                runCurrent()
-
-                assertThat(inAudioSharing).isTrue()
-            }
-        }
-    }
-
-    @Test
-    fun notInAudioSharing_returnFalse() {
-        with(kosmos) {
-            testScope.runTest {
-                audioSharingRepository.setInAudioSharing(false)
-
-                val inAudioSharing by collectLastValue(underTest.isInAudioSharing)
-                runCurrent()
-
-                assertThat(inAudioSharing).isFalse()
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
index a1fcfcd..c9d147b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
@@ -49,6 +49,24 @@
     }
 
     @Test
+    fun handleInAudioSharingChange() {
+        with(kosmos) {
+            testScope.runTest {
+                with(audioSharingRepository) { setInAudioSharing(true) }
+                val inAudioSharing by collectLastValue(underTest.isInAudioSharing)
+                runCurrent()
+
+                Truth.assertThat(inAudioSharing).isEqualTo(true)
+
+                with(audioSharingRepository) { setInAudioSharing(false) }
+                runCurrent()
+
+                Truth.assertThat(inAudioSharing).isEqualTo(false)
+            }
+        }
+    }
+
+    @Test
     fun handlePrimaryGroupChange_nullVolume() {
         with(kosmos) {
             testScope.runTest {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 0da252d..60fff28 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -104,6 +104,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.compose.animation.scene.ObservableTransitionState;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.foldables.FoldGracePeriodProvider;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -119,6 +120,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -140,6 +142,9 @@
 import com.android.systemui.plugins.clocks.WeatherData;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Scenes;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -150,6 +155,7 @@
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -279,6 +285,9 @@
     private final UserTracker mUserTracker;
     private final KeyguardUpdateMonitorLogger mLogger;
     private final boolean mIsSystemUser;
+    private final Provider<JavaAdapter> mJavaAdapter;
+    private final Provider<SceneInteractor> mSceneInteractor;
+    private final Provider<AlternateBouncerInteractor> mAlternateBouncerInteractor;
     private final AuthController mAuthController;
     private final UiEventLogger mUiEventLogger;
     private final Set<String> mAllowFingerprintOnOccludingActivitiesFromPackage;
@@ -563,7 +572,7 @@
      */
     private boolean shouldDismissKeyguardOnTrustGrantedWithCurrentUser(TrustGrantFlags flags) {
         final boolean isBouncerShowing =
-                mPrimaryBouncerIsOrWillBeShowing || mAlternateBouncerShowing;
+                isPrimaryBouncerShowingOrWillBeShowing() || isAlternateBouncerShowing();
         return (flags.isInitiatedByUser() || flags.dismissKeyguardRequested())
                 && (mDeviceInteractive || flags.temporaryAndRenewable())
                 && (isBouncerShowing || flags.dismissKeyguardRequested());
@@ -1170,8 +1179,8 @@
         Assert.isMainThread();
         String reason =
                 mKeyguardBypassController.canBypass() ? "bypass"
-                        : mAlternateBouncerShowing ? "alternateBouncer"
-                                : mPrimaryBouncerFullyShown ? "bouncer"
+                        : isAlternateBouncerShowing() ? "alternateBouncer"
+                                : isPrimaryBouncerFullyShown() ? "bouncer"
                                         : "udfpsFpDown";
         requestActiveUnlock(
                 ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
@@ -2169,7 +2178,10 @@
             Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider,
             TaskStackChangeListeners taskStackChangeListeners,
             SelectedUserInteractor selectedUserInteractor,
-            IActivityTaskManager activityTaskManagerService) {
+            IActivityTaskManager activityTaskManagerService,
+            Provider<AlternateBouncerInteractor> alternateBouncerInteractor,
+            Provider<JavaAdapter> javaAdapter,
+            Provider<SceneInteractor> sceneInteractor) {
         mContext = context;
         mSubscriptionManager = subscriptionManager;
         mUserTracker = userTracker;
@@ -2214,6 +2226,9 @@
 
         mFingerprintInteractiveToAuthProvider = interactiveToAuthProvider.orElse(null);
         mIsSystemUser = mUserManager.isSystemUser();
+        mAlternateBouncerInteractor = alternateBouncerInteractor;
+        mJavaAdapter = javaAdapter;
+        mSceneInteractor = sceneInteractor;
 
         mHandler = new Handler(mainLooper) {
             @Override
@@ -2470,6 +2485,30 @@
         mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.System.TIME_12_24),
                 false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
+
+        if (SceneContainerFlag.isEnabled()) {
+            mJavaAdapter.get().alwaysCollectFlow(
+                    mAlternateBouncerInteractor.get().isVisible(),
+                    this::onAlternateBouncerVisibilityChange);
+            mJavaAdapter.get().alwaysCollectFlow(
+                    mSceneInteractor.get().getTransitionState(),
+                    this::onTransitionStateChanged
+            );
+        }
+    }
+
+    @VisibleForTesting
+    void onAlternateBouncerVisibilityChange(boolean isAlternateBouncerVisible) {
+        setAlternateBouncerShowing(isAlternateBouncerVisible);
+    }
+
+
+    @VisibleForTesting
+    void onTransitionStateChanged(ObservableTransitionState transitionState) {
+        int primaryBouncerFullyShown = isPrimaryBouncerFullyShown(transitionState) ? 1 : 0;
+        int primaryBouncerIsOrWillBeShowing =
+                  isPrimaryBouncerShowingOrWillBeShowing(transitionState) ? 1 : 0;
+        handlePrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing, primaryBouncerFullyShown);
     }
 
     private void initializeSimState() {
@@ -2717,8 +2756,8 @@
         requestActiveUnlock(
                 requestOrigin,
                 extraReason, canFaceBypass
-                        || mAlternateBouncerShowing
-                        || mPrimaryBouncerFullyShown
+                        || isAlternateBouncerShowing()
+                        || isPrimaryBouncerFullyShown()
                         || mAuthController.isUdfpsFingerDown());
     }
 
@@ -2739,7 +2778,7 @@
      */
     public void setAlternateBouncerShowing(boolean showing) {
         mAlternateBouncerShowing = showing;
-        if (mAlternateBouncerShowing) {
+        if (isAlternateBouncerShowing()) {
             requestActiveUnlock(
                     ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
                     "alternateBouncer");
@@ -2747,6 +2786,45 @@
         updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
     }
 
+    private boolean isAlternateBouncerShowing() {
+        if (SceneContainerFlag.isEnabled()) {
+            return mAlternateBouncerInteractor.get().isVisibleState();
+        } else {
+            return mAlternateBouncerShowing;
+        }
+    }
+
+    private boolean isPrimaryBouncerShowingOrWillBeShowing() {
+        if (SceneContainerFlag.isEnabled()) {
+            return isPrimaryBouncerShowingOrWillBeShowing(
+                    mSceneInteractor.get().getTransitionState().getValue());
+        } else {
+            return mPrimaryBouncerIsOrWillBeShowing;
+        }
+    }
+
+    private boolean isPrimaryBouncerFullyShown() {
+        if (SceneContainerFlag.isEnabled()) {
+            return isPrimaryBouncerFullyShown(
+                    mSceneInteractor.get().getTransitionState().getValue());
+        } else {
+            return mPrimaryBouncerFullyShown;
+        }
+    }
+
+    private boolean isPrimaryBouncerShowingOrWillBeShowing(
+            ObservableTransitionState transitionState
+    ) {
+        SceneContainerFlag.assertInNewMode();
+        return isPrimaryBouncerFullyShown(transitionState)
+                || transitionState.isTransitioning(null, Scenes.Bouncer);
+    }
+
+    private boolean isPrimaryBouncerFullyShown(ObservableTransitionState transitionState) {
+        SceneContainerFlag.assertInNewMode();
+        return transitionState.isIdle(Scenes.Bouncer);
+    }
+
     /**
      * If the current state of the device allows for triggering active unlock. This does not
      * include active unlock availability.
@@ -2762,7 +2840,7 @@
     private boolean shouldTriggerActiveUnlock(boolean shouldLog) {
         // Triggers:
         final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant();
-        final boolean awakeKeyguard = mPrimaryBouncerFullyShown || mAlternateBouncerShowing
+        final boolean awakeKeyguard = isPrimaryBouncerFullyShown() || isAlternateBouncerShowing()
                 || (isKeyguardVisible() && !mGoingToSleep
                 && mStatusBarState != StatusBarState.SHADE_LOCKED);
 
@@ -2830,14 +2908,14 @@
         final boolean shouldListenKeyguardState =
                 isKeyguardVisible()
                         || !mDeviceInteractive
-                        || (mPrimaryBouncerIsOrWillBeShowing && !mKeyguardGoingAway)
+                        || (isPrimaryBouncerShowingOrWillBeShowing() && !mKeyguardGoingAway)
                         || mGoingToSleep
                         || shouldListenForFingerprintAssistant
                         || (mKeyguardOccluded && mIsDreaming)
                         || (mKeyguardOccluded && userDoesNotHaveTrust && mKeyguardShowing
                         && (mOccludingAppRequestingFp
                         || isUdfps
-                        || mAlternateBouncerShowing
+                        || isAlternateBouncerShowing()
                         || mAllowFingerprintOnCurrentOccludingActivity
                 )
             );
@@ -2856,7 +2934,7 @@
                         && !isUserInLockdown(user);
         final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
         final boolean shouldListenBouncerState =
-                !strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing;
+                !strongerAuthRequired || !isPrimaryBouncerShowingOrWillBeShowing();
 
         final boolean shouldListenUdfpsState = !isUdfps
                 || (!userCanSkipBouncer
@@ -2872,10 +2950,10 @@
                     user,
                     shouldListen,
                     mAllowFingerprintOnCurrentOccludingActivity,
-                    mAlternateBouncerShowing,
+                    isAlternateBouncerShowing(),
                     biometricEnabledForUser,
                     mBiometricPromptShowing,
-                    mPrimaryBouncerIsOrWillBeShowing,
+                    isPrimaryBouncerShowingOrWillBeShowing(),
                     userCanSkipBouncer,
                     mCredentialAttempted,
                     mDeviceInteractive,
@@ -3614,6 +3692,7 @@
      */
     public void sendPrimaryBouncerChanged(boolean primaryBouncerIsOrWillBeShowing,
             boolean primaryBouncerFullyShown) {
+        SceneContainerFlag.assertInLegacyMode();
         mLogger.logSendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing,
                 primaryBouncerFullyShown);
         Message message = mHandler.obtainMessage(MSG_KEYGUARD_BOUNCER_CHANGED);
@@ -4031,10 +4110,10 @@
             if (isUdfpsSupported()) {
                 pw.println("        udfpsEnrolled=" + isUdfpsEnrolled());
                 pw.println("        shouldListenForUdfps=" + shouldListenForFingerprint(true));
-                pw.println("        mPrimaryBouncerIsOrWillBeShowing="
-                        + mPrimaryBouncerIsOrWillBeShowing);
+                pw.println("        isPrimaryBouncerShowingOrWillBeShowing="
+                        + isPrimaryBouncerShowingOrWillBeShowing());
                 pw.println("        mStatusBarState=" + StatusBarState.toString(mStatusBarState));
-                pw.println("        mAlternateBouncerShowing=" + mAlternateBouncerShowing);
+                pw.println("        isAlternateBouncerShowing=" + isAlternateBouncerShowing());
             } else if (isSfpsSupported()) {
                 pw.println("        sfpsEnrolled=" + isSfpsEnrolled());
                 pw.println("        shouldListenForSfps=" + shouldListenForFingerprint(false));
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index baf8f5a..a301155 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -49,7 +49,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -140,7 +139,6 @@
     @Inject Lazy<SysUiState> mSysUiStateFlagsContainer;
     @Inject Lazy<CommandQueue> mCommandQueue;
     @Inject Lazy<UiEventLogger> mUiEventLogger;
-    @Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy;
     @Inject Lazy<FeatureFlags> mFeatureFlagsLazy;
     @Inject Lazy<NotificationSectionsManager> mNotificationSectionsManagerLazy;
     @Inject Lazy<ScreenOffAnimationController> mScreenOffAnimationController;
@@ -186,7 +184,6 @@
         mProviders.put(CommandQueue.class, mCommandQueue::get);
         mProviders.put(UiEventLogger.class, mUiEventLogger::get);
         mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get);
-        mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get);
         mProviders.put(NotificationSectionsManager.class, mNotificationSectionsManagerLazy::get);
         mProviders.put(ScreenOffAnimationController.class, mScreenOffAnimationController::get);
         mProviders.put(AmbientState.class, mAmbientStateLazy::get);
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index b7c02ea..6e01393 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -112,7 +112,11 @@
                         communalSceneInteractor.editModeState.value == EditModeState.STARTING ||
                             communalSceneInteractor.isLaunchingWidget.value
                     if (!delaySceneTransition) {
-                        communalSceneInteractor.changeScene(nextScene, nextTransition)
+                        communalSceneInteractor.changeScene(
+                            newScene = nextScene,
+                            loggingReason = "KTF syncing",
+                            transitionKey = nextTransition,
+                        )
                     }
                 }
                 .launchIn(applicationScope)
@@ -176,7 +180,10 @@
                     if (scene == CommunalScenes.Communal && isDreaming && timeoutJob == null) {
                         // If dreaming starts after timeout has expired, ex. if dream restarts under
                         // the hub, just close the hub immediately.
-                        communalSceneInteractor.changeScene(CommunalScenes.Blank)
+                        communalSceneInteractor.changeScene(
+                            CommunalScenes.Blank,
+                            "dream started after timeout",
+                        )
                     }
                 }
         }
@@ -201,7 +208,10 @@
                 bgScope.launch {
                     delay(screenTimeout.milliseconds)
                     if (isDreaming) {
-                        communalSceneInteractor.changeScene(CommunalScenes.Blank)
+                        communalSceneInteractor.changeScene(
+                            newScene = CommunalScenes.Blank,
+                            loggingReason = "hub timeout",
+                        )
                     }
                     timeoutJob = null
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 99bcc12..7181b15 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -329,8 +329,11 @@
     @Deprecated(
         "Use com.android.systemui.communal.domain.interactor.CommunalSceneInteractor instead"
     )
-    fun changeScene(newScene: SceneKey, transitionKey: TransitionKey? = null) =
-        communalSceneInteractor.changeScene(newScene, transitionKey)
+    fun changeScene(
+        newScene: SceneKey,
+        loggingReason: String,
+        transitionKey: TransitionKey? = null
+    ) = communalSceneInteractor.changeScene(newScene, loggingReason, transitionKey)
 
     fun setEditModeOpen(isOpen: Boolean) {
         _editModeOpen.value = isOpen
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
index e45a695..a0b9966 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -22,6 +22,7 @@
 import com.android.compose.animation.scene.TransitionKey
 import com.android.systemui.communal.data.repository.CommunalSceneRepository
 import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
+import com.android.systemui.communal.shared.log.CommunalSceneLogger
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
 import com.android.systemui.communal.shared.model.EditModeState
@@ -29,6 +30,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
+import com.android.systemui.util.kotlin.pairwiseBy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,8 +44,8 @@
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
@@ -51,7 +53,8 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    private val communalSceneRepository: CommunalSceneRepository,
+    private val repository: CommunalSceneRepository,
+    private val logger: CommunalSceneLogger,
 ) {
     private val _isLaunchingWidget = MutableStateFlow(false)
 
@@ -80,25 +83,39 @@
      */
     fun changeScene(
         newScene: SceneKey,
+        loggingReason: String,
         transitionKey: TransitionKey? = null,
         keyguardState: KeyguardState? = null,
     ) {
-        applicationScope.launch {
+        applicationScope.launch("$TAG#changeScene") {
+            logger.logSceneChangeRequested(
+                from = currentScene.value,
+                to = newScene,
+                reason = loggingReason,
+                isInstant = false,
+            )
             notifyListeners(newScene, keyguardState)
-            communalSceneRepository.changeScene(newScene, transitionKey)
+            repository.changeScene(newScene, transitionKey)
         }
     }
 
     /** Immediately snaps to the new scene. */
     fun snapToScene(
         newScene: SceneKey,
+        loggingReason: String,
         delayMillis: Long = 0,
         keyguardState: KeyguardState? = null
     ) {
         applicationScope.launch("$TAG#snapToScene") {
             delay(delayMillis)
+            logger.logSceneChangeRequested(
+                from = currentScene.value,
+                to = newScene,
+                reason = loggingReason,
+                isInstant = true,
+            )
             notifyListeners(newScene, keyguardState)
-            communalSceneRepository.snapToScene(newScene)
+            repository.snapToScene(newScene)
         }
     }
 
@@ -113,13 +130,30 @@
         if (_editModeState.value == EditModeState.STARTING) {
             return
         }
-        changeScene(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
+        changeScene(
+            CommunalScenes.Blank,
+            "activity start dismissing keyguard",
+            CommunalTransitionKeys.SimpleFade,
+        )
     }
 
     /**
      * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene].
      */
-    val currentScene: Flow<SceneKey> = communalSceneRepository.currentScene
+    val currentScene: StateFlow<SceneKey> =
+        repository.currentScene
+            .pairwiseBy(initialValue = repository.currentScene.value) { from, to ->
+                logger.logSceneChangeCommitted(
+                    from = from,
+                    to = to,
+                )
+                to
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = repository.currentScene.value,
+            )
 
     private val _editModeState = MutableStateFlow<EditModeState?>(null)
     /**
@@ -134,7 +168,13 @@
 
     /** Transition state of the hub mode. */
     val transitionState: StateFlow<ObservableTransitionState> =
-        communalSceneRepository.transitionState
+        repository.transitionState
+            .onEach { logger.logSceneTransition(it) }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = repository.transitionState.value,
+            )
 
     /**
      * Updates the transition state of the hub [SceneTransitionLayout].
@@ -142,7 +182,7 @@
      * Note that you must call is with `null` when the UI is done or risk a memory leak.
      */
     fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
-        communalSceneRepository.setTransitionState(transitionState)
+        repository.setTransitionState(transitionState)
     }
 
     /** Returns a flow that tracks the progress of transitions to the given scene from 0-1. */
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalSceneLogger.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalSceneLogger.kt
new file mode 100644
index 0000000..aed9215
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalSceneLogger.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.communal.shared.log
+
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.CommunalLog
+import javax.inject.Inject
+
+class CommunalSceneLogger @Inject constructor(@CommunalLog private val logBuffer: LogBuffer) {
+
+    fun logSceneChangeRequested(
+        from: SceneKey,
+        to: SceneKey,
+        reason: String,
+        isInstant: Boolean,
+    ) {
+        logBuffer.log(
+            tag = TAG,
+            level = LogLevel.INFO,
+            messageInitializer = {
+                str1 = from.toString()
+                str2 = to.toString()
+                str3 = reason
+                bool1 = isInstant
+            },
+            messagePrinter = {
+                buildString {
+                    append("Scene change requested: $str1 → $str2")
+                    if (isInstant) {
+                        append(" (instant)")
+                    }
+                    append(", reason: $str3")
+                }
+            },
+        )
+    }
+
+    fun logSceneChangeCommitted(
+        from: SceneKey,
+        to: SceneKey,
+    ) {
+        logBuffer.log(
+            tag = TAG,
+            level = LogLevel.INFO,
+            messageInitializer = {
+                str1 = from.toString()
+                str2 = to.toString()
+            },
+            messagePrinter = { "Scene change committed: $str1 → $str2" },
+        )
+    }
+
+    fun logSceneTransition(transitionState: ObservableTransitionState) {
+        when (transitionState) {
+            is ObservableTransitionState.Transition -> {
+                logBuffer.log(
+                    tag = TAG,
+                    level = LogLevel.INFO,
+                    messageInitializer = {
+                        str1 = transitionState.fromScene.toString()
+                        str2 = transitionState.toScene.toString()
+                    },
+                    messagePrinter = { "Scene transition started: $str1 → $str2" },
+                )
+            }
+            is ObservableTransitionState.Idle -> {
+                logBuffer.log(
+                    tag = TAG,
+                    level = LogLevel.INFO,
+                    messageInitializer = { str1 = transitionState.currentScene.toString() },
+                    messagePrinter = { "Scene transition idle on: $str1" },
+                )
+            }
+        }
+    }
+
+    companion object {
+        private const val TAG = "CommunalSceneLogger"
+    }
+}
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 d1a5a4b..b822133 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
@@ -106,10 +106,11 @@
      */
     fun changeScene(
         scene: SceneKey,
+        loggingReason: String,
         transitionKey: TransitionKey? = null,
         keyguardState: KeyguardState? = null
     ) {
-        communalSceneInteractor.changeScene(scene, transitionKey, keyguardState)
+        communalSceneInteractor.changeScene(scene, loggingReason, transitionKey, keyguardState)
     }
 
     fun setEditModeState(state: EditModeState?) = communalSceneInteractor.setEditModeState(state)
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 bbd8596..6239373 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
@@ -67,7 +67,10 @@
      * transition.
      */
     fun snapToCommunal() {
-        communalSceneInteractor.snapToScene(CommunalScenes.Communal)
+        communalSceneInteractor.snapToScene(
+            newScene = CommunalScenes.Communal,
+            loggingReason = "transition view model",
+        )
     }
 
     // Show UMO on glanceable hub immediately on transition into glanceable hub
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
index 0844462..e7cedc6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
@@ -42,6 +42,7 @@
         // TODO(b/330672236): move this to onTransitionAnimationEnd() without the delay.
         communalSceneInteractor.snapToScene(
             CommunalScenes.Blank,
+            "CommunalTransitionAnimatorController",
             ActivityTransitionAnimator.TIMINGS.totalDuration
         )
     }
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 668fef6..6d7cdc4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -218,9 +218,10 @@
         lifecycleScope.launch {
             communalViewModel.canShowEditMode.collect {
                 communalViewModel.changeScene(
-                    CommunalScenes.Blank,
-                    CommunalTransitionKeys.ToEditMode,
-                    KeyguardState.GONE,
+                    scene = CommunalScenes.Blank,
+                    loggingReason = "edit mode opening",
+                    transitionKey = CommunalTransitionKeys.ToEditMode,
+                    keyguardState = KeyguardState.GONE,
                 )
                 // wait till transitioned to Blank scene, then animate in communal content in
                 // edit mode
@@ -252,8 +253,9 @@
             communalViewModel.cleanupEditModeState()
 
             communalViewModel.changeScene(
-                CommunalScenes.Communal,
-                CommunalTransitionKeys.FromEditMode
+                scene = CommunalScenes.Communal,
+                loggingReason = "edit mode closing",
+                transitionKey = CommunalTransitionKeys.FromEditMode
             )
 
             // Wait for the current scene to be idle on communal.
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 4b9e5a0..0c1fb72 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -442,7 +442,9 @@
     @Override
     public void onWakeRequested() {
         mUiEventLogger.log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START);
-        mCommunalInteractor.changeScene(CommunalScenes.Communal, null);
+        mCommunalInteractor.changeScene(CommunalScenes.Communal,
+                "dream wake requested",
+                null);
     }
 
     private Lifecycle.State getLifecycleStateLocked() {
@@ -493,7 +495,7 @@
         mSystemDialogsCloser.closeSystemDialogs();
 
         // Hide glanceable hub (this is a nop if glanceable hub is not open).
-        mCommunalInteractor.changeScene(CommunalScenes.Blank, null);
+        mCommunalInteractor.changeScene(CommunalScenes.Blank, "dream come to front", null);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index 4b07f78..5c0335a6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -20,9 +20,9 @@
 import com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
@@ -51,6 +51,7 @@
     fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel,
     toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
     private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+    private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor,
     private val communalInteractor: CommunalInteractor,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val userTracker: UserTracker,
@@ -61,11 +62,9 @@
         val showGlanceableHub =
             communalInteractor.isCommunalEnabled.value &&
                 !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
-        if (showGlanceableHub && !glanceableHubAllowKeyguardWhenDreaming()) {
-            communalInteractor.changeScene(CommunalScenes.Communal)
-        } else {
-            toLockscreenTransitionViewModel.startTransition()
-        }
+        fromDreamingTransitionInteractor.startToLockscreenOrGlanceableHubTransition(
+            showGlanceableHub && !glanceableHubAllowKeyguardWhenDreaming()
+        )
     }
 
     val dreamOverlayTranslationX: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 0c12f8c..90aaf0d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -273,9 +273,10 @@
     private suspend fun transitionToGlanceableHub() {
         if (communalSceneKtfRefactor()) {
             communalSceneInteractor.changeScene(
-                CommunalScenes.Communal,
+                newScene = CommunalScenes.Communal,
+                loggingReason = "from dozing to hub",
                 // Immediately show the hub when transitioning from dozing to hub.
-                CommunalTransitionKeys.Immediately,
+                transitionKey = CommunalTransitionKeys.Immediately,
             )
         } else {
             startTransitionTo(KeyguardState.GLANCEABLE_HUB)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 7bf9c2f1..4666430 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -20,7 +20,9 @@
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launch
 import com.android.systemui.Flags.communalSceneKtfRefactor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+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.dagger.qualifiers.Main
@@ -58,6 +60,7 @@
     @Main mainDispatcher: CoroutineDispatcher,
     keyguardInteractor: KeyguardInteractor,
     private val glanceableHubTransitions: GlanceableHubTransitions,
+    private val communalSceneInteractor: CommunalSceneInteractor,
     private val communalSettingsInteractor: CommunalSettingsInteractor,
     powerInteractor: PowerInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
@@ -126,17 +129,24 @@
         }
     }
 
-    fun startToLockscreenTransition() {
+    fun startToLockscreenOrGlanceableHubTransition(openHub: Boolean) {
         scope.launch {
             if (
                 transitionInteractor.startedKeyguardState.replayCache.last() ==
                     KeyguardState.DREAMING
             ) {
                 if (powerInteractor.detailedWakefulness.value.isAwake()) {
-                    startTransitionTo(
-                        KeyguardState.LOCKSCREEN,
-                        ownerReason = "Dream has ended and device is awake"
-                    )
+                    if (openHub) {
+                        communalSceneInteractor.changeScene(
+                            newScene = CommunalScenes.Communal,
+                            loggingReason = "FromDreamingTransitionInteractor",
+                        )
+                    } else {
+                        startTransitionTo(
+                            KeyguardState.LOCKSCREEN,
+                            ownerReason = "Dream has ended and device is awake"
+                        )
+                    }
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index befcc9e..c9db26d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -159,6 +159,7 @@
                     if (communalSceneKtfRefactor()) {
                         communalSceneInteractor.changeScene(
                             newScene = CommunalScenes.Blank,
+                            loggingReason = "hub to dozing",
                             transitionKey = CommunalTransitionKeys.Immediately,
                             keyguardState = KeyguardState.DOZING,
                         )
@@ -182,6 +183,7 @@
                             if (communalSceneKtfRefactor()) {
                                 communalSceneInteractor.changeScene(
                                     newScene = CommunalScenes.Blank,
+                                    loggingReason = "hub to occluded (KeyguardWmStateRefactor)",
                                     transitionKey = CommunalTransitionKeys.SimpleFade,
                                     keyguardState = state,
                                 )
@@ -211,6 +213,7 @@
                     .collect { _ ->
                         communalSceneInteractor.changeScene(
                             newScene = CommunalScenes.Blank,
+                            loggingReason = "hub to occluded",
                             transitionKey = CommunalTransitionKeys.SimpleFade,
                             keyguardState = KeyguardState.OCCLUDED,
                         )
@@ -254,6 +257,7 @@
                         } else {
                             communalSceneInteractor.changeScene(
                                 newScene = CommunalScenes.Blank,
+                                loggingReason = "hub to gone",
                                 transitionKey = CommunalTransitionKeys.SimpleFade,
                                 keyguardState = KeyguardState.GONE
                             )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 905ca8e..7b6949f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -146,8 +146,9 @@
             if (SceneContainerFlag.isEnabled) return
             if (communalSceneKtfRefactor()) {
                 communalSceneInteractor.changeScene(
-                    CommunalScenes.Communal,
-                    CommunalTransitionKeys.SimpleFade
+                    newScene = CommunalScenes.Communal,
+                    loggingReason = "occluded to hub",
+                    transitionKey = CommunalTransitionKeys.SimpleFade
                 )
             } else {
                 startTransitionTo(KeyguardState.GLANCEABLE_HUB)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 2823b93..0118f8e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -175,7 +175,10 @@
                 !communalSceneInteractor.isLaunchingWidget.value &&
                 communalSceneInteractor.editModeState.value == null
         ) {
-            communalSceneInteractor.snapToScene(CommunalScenes.Blank)
+            communalSceneInteractor.snapToScene(
+                newScene = CommunalScenes.Blank,
+                loggingReason = "FromPrimaryBouncerTransitionInteractor",
+            )
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt
index 34c1436..38f5d3e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt
@@ -50,5 +50,9 @@
             }
             .distinctUntilChanged()
 
-    fun openCommunalHub() = communalInteractor.changeScene(CommunalScenes.Communal)
+    fun openCommunalHub() =
+        communalInteractor.changeScene(
+            newScene = CommunalScenes.Communal,
+            loggingReason = "accessibility",
+        )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index b5ec7a6..10605b2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -18,7 +18,6 @@
 
 import com.android.app.animation.Interpolators.EMPHASIZED
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
@@ -39,10 +38,8 @@
 class DreamingToLockscreenTransitionViewModel
 @Inject
 constructor(
-    private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
-    fun startTransition() = fromDreamingTransitionInteractor.startToLockscreenTransition()
 
     private val transitionAnimation =
         animationFlow.setup(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt
index 9fa6769..bb238f2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.fragments.FragmentService
+import com.android.systemui.qs.composefragment.QSFragmentCompose
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -31,13 +32,18 @@
 @Inject
 constructor(
     private val fragmentService: FragmentService,
-    private val qsFragmentLegacyProvider: Provider<QSFragmentLegacy>
+    private val qsFragmentLegacyProvider: Provider<QSFragmentLegacy>,
+    private val qsFragmentComposeProvider: Provider<QSFragmentCompose>,
 ) : CoreStartable {
     override fun start() {
         fragmentService.addFragmentInstantiationProvider(
             QSFragmentLegacy::class.java,
             qsFragmentLegacyProvider
         )
+        fragmentService.addFragmentInstantiationProvider(
+            QSFragmentCompose::class.java,
+            qsFragmentComposeProvider
+        )
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSModesEvent.kt b/packages/SystemUI/src/com/android/systemui/qs/QSModesEvent.kt
new file mode 100644
index 0000000..1891c41
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSModesEvent.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+/** Events of user interactions with modes from the QS Modes dialog. {@see ModesDialogViewModel} */
+enum class QSModesEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+    @UiEvent(doc = "User turned manual Do Not Disturb on via modes dialog") QS_MODES_DND_ON(1870),
+    @UiEvent(doc = "User turned manual Do Not Disturb off via modes dialog") QS_MODES_DND_OFF(1871),
+    @UiEvent(doc = "User opened mode settings from the Do Not Disturb tile in the modes dialog")
+    QS_MODES_DND_SETTINGS(1872),
+    @UiEvent(doc = "User turned automatic mode on via modes dialog") QS_MODES_MODE_ON(1873),
+    @UiEvent(doc = "User turned automatic mode off via modes dialog") QS_MODES_MODE_OFF(1874),
+    @UiEvent(doc = "User opened mode settings from a mode tile in the modes dialog")
+    QS_MODES_MODE_SETTINGS(1875),
+    @UiEvent(doc = "User clicked on Settings from the modes dialog") QS_MODES_SETTINGS(1876),
+    @UiEvent(doc = "User clicked on Do Not Disturb tile, opening the time selection dialog")
+    QS_MODES_DURATION_DIALOG(1879);
+
+    override fun getId() = _id
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
new file mode 100644
index 0000000..5d81d4f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -0,0 +1,442 @@
+/*
+ * 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.composefragment
+
+import android.annotation.SuppressLint
+import android.graphics.Rect
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.OnBackPressedDispatcherOwner
+import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.positionInRoot
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.round
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.compose.modifiers.height
+import com.android.compose.modifiers.padding
+import com.android.compose.theme.PlatformTheme
+import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.qs.QS
+import com.android.systemui.plugins.qs.QSContainerController
+import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel
+import com.android.systemui.qs.flags.QSComposeFragment
+import com.android.systemui.qs.footer.ui.compose.FooterActions
+import com.android.systemui.qs.panels.ui.compose.QuickQuickSettings
+import com.android.systemui.qs.ui.composable.QuickSettingsTheme
+import com.android.systemui.qs.ui.composable.ShadeBody
+import com.android.systemui.res.R
+import com.android.systemui.util.LifecycleFragment
+import java.util.function.Consumer
+import javax.inject.Inject
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+@SuppressLint("ValidFragment")
+class QSFragmentCompose
+@Inject
+constructor(
+    private val qsFragmentComposeViewModelFactory: QSFragmentComposeViewModel.Factory,
+) : LifecycleFragment(), QS {
+
+    private val scrollListener = MutableStateFlow<QS.ScrollListener?>(null)
+    private val heightListener = MutableStateFlow<QS.HeightListener?>(null)
+    private val qsContainerController = MutableStateFlow<QSContainerController?>(null)
+
+    private lateinit var viewModel: QSFragmentComposeViewModel
+
+    // Starting with a non-zero value makes it so that it has a non-zero height on first expansion
+    // This is important for `QuickSettingsControllerImpl.mMinExpansionHeight` to detect a "change".
+    private val qqsHeight = MutableStateFlow(1)
+    private val qsHeight = MutableStateFlow(0)
+    private val qqsVisible = MutableStateFlow(false)
+    private val qqsPositionOnRoot = Rect()
+    private val composeViewPositionOnScreen = Rect()
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        QSComposeFragment.isUnexpectedlyInLegacyMode()
+        viewModel = qsFragmentComposeViewModelFactory.create(lifecycleScope)
+
+        setListenerCollections()
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        val context = inflater.context
+        return ComposeView(context).apply {
+            setBackPressedDispatcher()
+            setContent {
+                PlatformTheme {
+                    val visible by viewModel.qsVisible.collectAsStateWithLifecycle()
+                    val qsState by viewModel.expansionState.collectAsStateWithLifecycle()
+
+                    AnimatedVisibility(
+                        visible = visible,
+                        modifier = Modifier.windowInsetsPadding(WindowInsets.navigationBars)
+                    ) {
+                        AnimatedContent(targetState = qsState) {
+                            when (it) {
+                                QSFragmentComposeViewModel.QSExpansionState.QQS -> {
+                                    QuickQuickSettingsElement()
+                                }
+                                QSFragmentComposeViewModel.QSExpansionState.QS -> {
+                                    QuickSettingsElement()
+                                }
+                                else -> {}
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    override fun setPanelView(notificationPanelView: QS.HeightListener?) {
+        heightListener.value = notificationPanelView
+    }
+
+    override fun hideImmediately() {
+        //        view?.animate()?.cancel()
+        //        view?.y = -qsMinExpansionHeight.toFloat()
+    }
+
+    override fun getQsMinExpansionHeight(): Int {
+        // TODO (b/353253277) implement split screen
+        return qqsHeight.value
+    }
+
+    override fun getDesiredHeight(): Int {
+        /*
+         * Looking at the code, it seems that
+         * * If customizing, then the height is that of the view post-layout, which is set by
+         *   QSContainerImpl.calculateContainerHeight, which is the height the customizer takes
+         * * If not customizing, it's the measured height. So we may want to surface that.
+         */
+        return view?.height ?: 0
+    }
+
+    override fun setHeightOverride(desiredHeight: Int) {
+        viewModel.heightOverrideValue = desiredHeight
+    }
+
+    override fun setHeaderClickable(qsExpansionEnabled: Boolean) {
+        // Empty method
+    }
+
+    override fun isCustomizing(): Boolean {
+        return viewModel.containerViewModel.editModeViewModel.isEditing.value
+    }
+
+    override fun closeCustomizer() {
+        viewModel.containerViewModel.editModeViewModel.stopEditing()
+    }
+
+    override fun setOverscrolling(overscrolling: Boolean) {
+        viewModel.stackScrollerOverscrollingValue = overscrolling
+    }
+
+    override fun setExpanded(qsExpanded: Boolean) {
+        viewModel.isQSExpanded = qsExpanded
+    }
+
+    override fun setListening(listening: Boolean) {
+        // Not needed, views start listening and collection when composed
+    }
+
+    override fun setQsVisible(qsVisible: Boolean) {
+        viewModel.isQSVisible = qsVisible
+    }
+
+    override fun isShowingDetail(): Boolean {
+        return isCustomizing
+    }
+
+    override fun closeDetail() {
+        closeCustomizer()
+    }
+
+    override fun animateHeaderSlidingOut() {
+        // TODO(b/353254353)
+    }
+
+    override fun setQsExpansion(
+        qsExpansionFraction: Float,
+        panelExpansionFraction: Float,
+        headerTranslation: Float,
+        squishinessFraction: Float
+    ) {
+        viewModel.qsExpansionValue = qsExpansionFraction
+        viewModel.panelExpansionFractionValue = panelExpansionFraction
+        viewModel.squishinessFractionValue = squishinessFraction
+
+        // TODO(b/353254353) Handle header translation
+    }
+
+    override fun setHeaderListening(listening: Boolean) {
+        // Not needed, header will start listening as soon as it's composed
+    }
+
+    override fun notifyCustomizeChanged() {
+        // Not needed, only called from inside customizer
+    }
+
+    override fun setContainerController(controller: QSContainerController?) {
+        qsContainerController.value = controller
+    }
+
+    override fun setCollapseExpandAction(action: Runnable?) {
+        // Nothing to do yet. But this should be wired to a11y
+    }
+
+    override fun getHeightDiff(): Int {
+        return 0 // For now TODO(b/353254353)
+    }
+
+    override fun getHeader(): View? {
+        QSComposeFragment.isUnexpectedlyInLegacyMode()
+        return null
+    }
+
+    override fun setShouldUpdateSquishinessOnMedia(shouldUpdate: Boolean) {
+        super.setShouldUpdateSquishinessOnMedia(shouldUpdate)
+        // TODO (b/353253280)
+    }
+
+    override fun setInSplitShade(shouldTranslate: Boolean) {
+        // TODO (b/356435605)
+    }
+
+    override fun setTransitionToFullShadeProgress(
+        isTransitioningToFullShade: Boolean,
+        qsTransitionFraction: Float,
+        qsSquishinessFraction: Float
+    ) {
+        super.setTransitionToFullShadeProgress(
+            isTransitioningToFullShade,
+            qsTransitionFraction,
+            qsSquishinessFraction
+        )
+    }
+
+    override fun setFancyClipping(
+        leftInset: Int,
+        top: Int,
+        rightInset: Int,
+        bottom: Int,
+        cornerRadius: Int,
+        visible: Boolean,
+        fullWidth: Boolean
+    ) {}
+
+    override fun isFullyCollapsed(): Boolean {
+        return !viewModel.isQSVisible
+    }
+
+    override fun setCollapsedMediaVisibilityChangedListener(listener: Consumer<Boolean>?) {
+        // TODO (b/353253280)
+    }
+
+    override fun setScrollListener(scrollListener: QS.ScrollListener?) {
+        this.scrollListener.value = scrollListener
+    }
+
+    override fun setOverScrollAmount(overScrollAmount: Int) {
+        super.setOverScrollAmount(overScrollAmount)
+    }
+
+    override fun setIsNotificationPanelFullWidth(isFullWidth: Boolean) {
+        viewModel.isSmallScreenValue = isFullWidth
+    }
+
+    override fun getHeaderTop(): Int {
+        return viewModel.qqsHeaderHeight.value
+    }
+
+    override fun getHeaderBottom(): Int {
+        return headerTop + qqsHeight.value
+    }
+
+    override fun getHeaderLeft(): Int {
+        return qqsPositionOnRoot.left
+    }
+
+    override fun getHeaderBoundsOnScreen(outBounds: Rect) {
+        outBounds.set(qqsPositionOnRoot)
+        view?.getBoundsOnScreen(composeViewPositionOnScreen)
+            ?: run { composeViewPositionOnScreen.setEmpty() }
+        qqsPositionOnRoot.offset(composeViewPositionOnScreen.left, composeViewPositionOnScreen.top)
+    }
+
+    override fun isHeaderShown(): Boolean {
+        return qqsVisible.value
+    }
+
+    private fun setListenerCollections() {
+        lifecycleScope.launch {
+            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    //                    TODO
+                    //                    setListenerJob(
+                    //                            scrollListener,
+                    //
+                    //                    )
+                }
+                launch {
+                    setListenerJob(
+                        heightListener,
+                        viewModel.containerViewModel.editModeViewModel.isEditing
+                    ) {
+                        onQsHeightChanged()
+                    }
+                }
+                launch {
+                    setListenerJob(
+                        qsContainerController,
+                        viewModel.containerViewModel.editModeViewModel.isEditing
+                    ) {
+                        setCustomizerShowing(it)
+                    }
+                }
+            }
+        }
+    }
+
+    @Composable
+    private fun QuickQuickSettingsElement() {
+        val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle()
+        DisposableEffect(Unit) {
+            qqsVisible.value = true
+
+            onDispose { qqsVisible.value = false }
+        }
+        Column(modifier = Modifier.sysuiResTag("quick_qs_panel")) {
+            QuickQuickSettings(
+                viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel,
+                modifier =
+                    Modifier.onGloballyPositioned { coordinates ->
+                            val (leftFromRoot, topFromRoot) = coordinates.positionInRoot().round()
+                            val (width, height) = coordinates.size
+                            qqsPositionOnRoot.set(
+                                leftFromRoot,
+                                topFromRoot,
+                                leftFromRoot + width,
+                                topFromRoot + height
+                            )
+                        }
+                        .layout { measurable, constraints ->
+                            val placeable = measurable.measure(constraints)
+                            qqsHeight.value = placeable.height
+
+                            layout(placeable.width, placeable.height) { placeable.place(0, 0) }
+                        }
+                        .padding(top = { qqsPadding })
+            )
+            Spacer(modifier = Modifier.weight(1f))
+        }
+    }
+
+    @Composable
+    private fun QuickSettingsElement() {
+        val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle()
+        val qsExtraPadding = dimensionResource(R.dimen.qs_panel_padding_top)
+        Column {
+            Box(modifier = Modifier.fillMaxSize().weight(1f)) {
+                Column {
+                    Spacer(modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() })
+                    ShadeBody(viewModel = viewModel.containerViewModel)
+                }
+            }
+            QuickSettingsTheme {
+                FooterActions(
+                    viewModel = viewModel.footerActionsViewModel,
+                    qsVisibilityLifecycleOwner = this@QSFragmentCompose,
+                    modifier = Modifier.sysuiResTag("qs_footer_actions")
+                )
+            }
+        }
+    }
+}
+
+private fun View.setBackPressedDispatcher() {
+    repeatWhenAttached {
+        repeatOnLifecycle(Lifecycle.State.CREATED) {
+            setViewTreeOnBackPressedDispatcherOwner(
+                object : OnBackPressedDispatcherOwner {
+                    override val onBackPressedDispatcher =
+                        OnBackPressedDispatcher().apply {
+                            setOnBackInvokedDispatcher(it.viewRootImpl.onBackInvokedDispatcher)
+                        }
+
+                    override val lifecycle: Lifecycle = this@repeatWhenAttached.lifecycle
+                }
+            )
+        }
+    }
+}
+
+private suspend inline fun <Listener : Any, Data> setListenerJob(
+    listenerFlow: MutableStateFlow<Listener?>,
+    dataFlow: Flow<Data>,
+    crossinline onCollect: suspend Listener.(Data) -> Unit
+) {
+    coroutineScope {
+        try {
+            listenerFlow.collectLatest { listenerOrNull ->
+                listenerOrNull?.let { currentListener ->
+                    launch {
+                        // Called when editing mode changes
+                        dataFlow.collect { currentListener.onCollect(it) }
+                    }
+                }
+            }
+            awaitCancellation()
+        } finally {
+            listenerFlow.value = null
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
new file mode 100644
index 0000000..9e109e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -0,0 +1,196 @@
+/*
+ * 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.composefragment.viewmodel
+
+import android.content.res.Resources
+import android.graphics.Rect
+import androidx.lifecycle.LifecycleCoroutineScope
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
+import com.android.systemui.shade.LargeScreenHeaderHelper
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.util.LargeScreenUtils
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+class QSFragmentComposeViewModel
+@AssistedInject
+constructor(
+    val containerViewModel: QuickSettingsContainerViewModel,
+    @Main private val resources: Resources,
+    private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
+    private val footerActionsController: FooterActionsController,
+    private val sysuiStatusBarStateController: SysuiStatusBarStateController,
+    private val keyguardBypassController: KeyguardBypassController,
+    private val disableFlagsRepository: DisableFlagsRepository,
+    private val largeScreenShadeInterpolator: LargeScreenShadeInterpolator,
+    private val configurationInteractor: ConfigurationInteractor,
+    private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
+    @Assisted private val lifecycleScope: LifecycleCoroutineScope,
+) {
+    val footerActionsViewModel =
+        footerActionsViewModelFactory.create(lifecycleScope).also {
+            lifecycleScope.launch { footerActionsController.init() }
+        }
+
+    private val _qsBounds = MutableStateFlow(Rect())
+
+    private val _qsExpanded = MutableStateFlow(false)
+    var isQSExpanded: Boolean
+        get() = _qsExpanded.value
+        set(value) {
+            _qsExpanded.value = value
+        }
+
+    private val _qsVisible = MutableStateFlow(false)
+    val qsVisible = _qsVisible.asStateFlow()
+    var isQSVisible: Boolean
+        get() = qsVisible.value
+        set(value) {
+            _qsVisible.value = value
+        }
+
+    private val _qsExpansion = MutableStateFlow(0f)
+    var qsExpansionValue: Float
+        get() = _qsExpansion.value
+        set(value) {
+            _qsExpansion.value = value
+        }
+
+    private val _panelFraction = MutableStateFlow(0f)
+    var panelExpansionFractionValue: Float
+        get() = _panelFraction.value
+        set(value) {
+            _panelFraction.value = value
+        }
+
+    private val _squishinessFraction = MutableStateFlow(0f)
+    var squishinessFractionValue: Float
+        get() = _squishinessFraction.value
+        set(value) {
+            _squishinessFraction.value = value
+        }
+
+    val qqsHeaderHeight =
+        configurationInteractor.onAnyConfigurationChange
+            .map {
+                if (LargeScreenUtils.shouldUseLargeScreenShadeHeader(resources)) {
+                    0
+                } else {
+                    largeScreenHeaderHelper.getLargeScreenHeaderHeight()
+                }
+            }
+            .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), 0)
+
+    private val _headerAnimating = MutableStateFlow(false)
+
+    private val _stackScrollerOverscrolling = MutableStateFlow(false)
+    var stackScrollerOverscrollingValue: Boolean
+        get() = _stackScrollerOverscrolling.value
+        set(value) {
+            _stackScrollerOverscrolling.value = value
+        }
+
+    private val qsDisabled =
+        disableFlagsRepository.disableFlags
+            .map { !it.isQuickSettingsEnabled() }
+            .stateIn(
+                lifecycleScope,
+                SharingStarted.WhileSubscribed(),
+                !disableFlagsRepository.disableFlags.value.isQuickSettingsEnabled()
+            )
+
+    private val _showCollapsedOnKeyguard = MutableStateFlow(false)
+
+    private val _keyguardAndExpanded = MutableStateFlow(false)
+
+    private val _statusBarState = MutableStateFlow(-1)
+
+    private val _viewHeight = MutableStateFlow(0)
+
+    private val _headerTranslation = MutableStateFlow(0f)
+
+    private val _inSplitShade = MutableStateFlow(false)
+
+    private val _transitioningToFullShade = MutableStateFlow(false)
+
+    private val _lockscreenToShadeProgress = MutableStateFlow(false)
+
+    private val _overscrolling = MutableStateFlow(false)
+
+    private val _isSmallScreen = MutableStateFlow(false)
+    var isSmallScreenValue: Boolean
+        get() = _isSmallScreen.value
+        set(value) {
+            _isSmallScreen.value = value
+        }
+
+    private val _shouldUpdateMediaSquishiness = MutableStateFlow(false)
+
+    private val _heightOverride = MutableStateFlow(-1)
+    val heightOverride = _heightOverride.asStateFlow()
+    var heightOverrideValue: Int
+        get() = heightOverride.value
+        set(value) {
+            _heightOverride.value = value
+        }
+
+    val expansionState: StateFlow<QSExpansionState> =
+        combine(
+                _stackScrollerOverscrolling,
+                _qsExpanded,
+                _qsExpansion,
+            ) { args: Array<Any> ->
+                val expansion = args[2] as Float
+                if (expansion > 0.5f) {
+                    QSExpansionState.QS
+                } else {
+                    QSExpansionState.QQS
+                }
+            }
+            .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), QSExpansionState.QQS)
+
+    @AssistedFactory
+    interface Factory {
+        fun create(lifecycleScope: LifecycleCoroutineScope): QSFragmentComposeViewModel
+    }
+
+    sealed interface QSExpansionState {
+        data object QQS : QSExpansionState
+
+        data object QS : QSExpansionState
+
+        @JvmInline value class Expanding(val progress: Float) : QSExpansionState
+
+        @JvmInline value class Collapsing(val progress: Float) : QSExpansionState
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index ba45d17..6dc101a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -21,6 +21,7 @@
 import android.view.ContextThemeWrapper
 import androidx.lifecycle.DefaultLifecycleObserver
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleCoroutineScope
 import androidx.lifecycle.LifecycleOwner
 import com.android.settingslib.Utils
 import com.android.systemui.animation.Expandable
@@ -41,6 +42,7 @@
 import javax.inject.Named
 import javax.inject.Provider
 import kotlin.math.max
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -48,6 +50,8 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
 
 private const val TAG = "FooterActionsViewModel"
 
@@ -140,6 +144,30 @@
                 showPowerButton,
             )
         }
+
+        fun create(lifecycleCoroutineScope: LifecycleCoroutineScope): FooterActionsViewModel {
+            val globalActionsDialogLite = globalActionsDialogLiteProvider.get()
+            if (lifecycleCoroutineScope.isActive) {
+                lifecycleCoroutineScope.launch {
+                    try {
+                        awaitCancellation()
+                    } finally {
+                        globalActionsDialogLite.destroy()
+                    }
+                }
+            } else {
+                globalActionsDialogLite.destroy()
+            }
+
+            return FooterActionsViewModel(
+                context,
+                footerActionsInteractor,
+                falsingManager,
+                globalActionsDialogLite,
+                activityStarter,
+                showPowerButton,
+            )
+        }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
index 2ee957e..08a56bf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
@@ -39,6 +39,7 @@
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType
 import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.FooterHeight
 import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.InterPageSpacing
@@ -77,7 +78,7 @@
         Column {
             HorizontalPager(
                 state = pagerState,
-                modifier = Modifier,
+                modifier = Modifier.sysuiResTag("qs_pager"),
                 pageSpacing = if (pages.size > 1) InterPageSpacing else 0.dp,
                 beyondViewportPageCount = 1,
                 verticalAlignment = Alignment.Top,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index af3803b..a9027ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -25,6 +25,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.dimensionResource
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
 import com.android.systemui.res.R
 
@@ -44,7 +45,10 @@
     }
     val columns by viewModel.columns.collectAsStateWithLifecycle()
 
-    TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
+    TileLazyGrid(
+        modifier = modifier.sysuiResTag("qqs_tile_layout"),
+        columns = GridCells.Fixed(columns)
+    ) {
         items(
             tiles.size,
             key = { index -> sizedTiles[index].tile.spec.spec },
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index 7e6ccd6..9c0701e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -22,7 +22,6 @@
 import android.service.quicksettings.Tile.STATE_ACTIVE
 import android.service.quicksettings.Tile.STATE_INACTIVE
 import android.text.TextUtils
-import androidx.appcompat.content.res.AppCompatResources
 import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.fadeIn
@@ -593,15 +592,15 @@
 }
 
 @Composable
-private fun getTileIcon(icon: Supplier<QSTile.Icon>): Icon {
+private fun getTileIcon(icon: Supplier<QSTile.Icon?>): Icon {
     val context = LocalContext.current
-    return icon.get().let {
+    return icon.get()?.let {
         if (it is QSTileImpl.ResourceIcon) {
             Icon.Resource(it.resId, null)
         } else {
             Icon.Loaded(it.getDrawable(context), null)
         }
-    }
+    } ?: Icon.Resource(R.drawable.ic_error_outline, null)
 }
 
 @OptIn(ExperimentalAnimationGraphicsApi::class)
@@ -618,7 +617,7 @@
         remember(icon, context) {
             when (icon) {
                 is Icon.Loaded -> icon.drawable
-                is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res)
+                is Icon.Resource -> context.getDrawable(icon.res)
             }
         }
     if (loadedDrawable !is Animatable) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
index 4ec59c9..c83e3b2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
@@ -25,7 +25,7 @@
     val label: String,
     val secondaryLabel: String,
     val state: Int,
-    val icon: Supplier<QSTile.Icon>,
+    val icon: Supplier<QSTile.Icon?>,
 )
 
 fun QSTile.State.toUiState(): TileUiState {
@@ -33,6 +33,6 @@
         label?.toString() ?: "",
         secondaryLabel?.toString() ?: "",
         state,
-        icon?.let { Supplier { icon } } ?: iconSupplier,
+        icon?.let { Supplier { icon } } ?: iconSupplier ?: Supplier { null },
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
index 107bf1e..d4ef42c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
@@ -613,8 +613,9 @@
                     super.onTransitionAnimationStart(isExpandingFullyAbove)
                     if (Flags.communalHub()) {
                         communalSceneInteractor.snapToScene(
-                            CommunalScenes.Blank,
-                            ActivityTransitionAnimator.TIMINGS.totalDuration
+                            newScene = CommunalScenes.Blank,
+                            loggingReason = "ActivityStarterInternalImpl",
+                            delayMillis = ActivityTransitionAnimator.TIMINGS.totalDuration
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index c4fbc37..94dd9bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -160,6 +160,8 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.qs.QSFragmentLegacy;
 import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.qs.composefragment.QSFragmentCompose;
+import com.android.systemui.qs.flags.QSComposeFragment;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
@@ -1432,9 +1434,15 @@
     }
 
     protected QS createDefaultQSFragment() {
+        Class<? extends QS> klass;
+        if (QSComposeFragment.isEnabled()) {
+            klass = QSFragmentCompose.class;
+        } else {
+            klass = QSFragmentLegacy.class;
+        }
         return mFragmentService
                 .getFragmentHostManager(getNotificationShadeWindowView())
-                .create(QSFragmentLegacy.class);
+                .create(klass);
     }
 
     private void setUpPresenter() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 04604e0..dda02db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -32,6 +32,8 @@
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.Flags;
@@ -47,7 +49,6 @@
 
 public class PhoneStatusBarView extends FrameLayout {
     private static final String TAG = "PhoneStatusBarView";
-    private final StatusBarContentInsetsProvider mContentInsetsProvider;
     private final StatusBarWindowController mStatusBarWindowController;
 
     private int mRotationOrientation = -1;
@@ -60,6 +61,10 @@
     private int mStatusBarHeight;
     @Nullable
     private Gefingerpoken mTouchEventHandler;
+    @Nullable
+    private HasCornerCutoutFetcher mHasCornerCutoutFetcher;
+    @Nullable
+    private InsetsFetcher mInsetsFetcher;
     private int mDensity;
     private float mFontScale;
 
@@ -70,7 +75,6 @@
 
     public PhoneStatusBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mContentInsetsProvider = Dependency.get(StatusBarContentInsetsProvider.class);
         mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
     }
 
@@ -78,6 +82,14 @@
         mTouchEventHandler = handler;
     }
 
+    void setHasCornerCutoutFetcher(@NonNull HasCornerCutoutFetcher cornerCutoutFetcher) {
+        mHasCornerCutoutFetcher = cornerCutoutFetcher;
+    }
+
+    void setInsetsFetcher(@NonNull InsetsFetcher insetsFetcher) {
+        mInsetsFetcher = insetsFetcher;
+    }
+
     void init(StatusBarUserChipViewModel viewModel) {
         StatusBarUserSwitcherContainer container = findViewById(R.id.user_switcher_container);
         StatusBarUserChipViewBinder.bind(container, viewModel);
@@ -270,7 +282,14 @@
             return;
         }
 
-        boolean hasCornerCutout = mContentInsetsProvider.currentRotationHasCornerCutout();
+        boolean hasCornerCutout;
+        if (mHasCornerCutoutFetcher != null) {
+            hasCornerCutout = mHasCornerCutoutFetcher.fetchHasCornerCutout();
+        } else {
+            Log.e(TAG, "mHasCornerCutoutFetcher unexpectedly null");
+            hasCornerCutout = true;
+        }
+
         if (mDisplayCutout == null || mDisplayCutout.isEmpty() || hasCornerCutout) {
             mCutoutSpace.setVisibility(View.GONE);
             return;
@@ -288,8 +307,12 @@
     }
 
     private void updateSafeInsets() {
-        Insets insets = mContentInsetsProvider
-                .getStatusBarContentInsetsForCurrentRotation();
+        if (mInsetsFetcher == null) {
+            Log.e(TAG, "mInsetsFetcher unexpectedly null");
+            return;
+        }
+
+        Insets insets  = mInsetsFetcher.fetchInsets();
         setPadding(
                 insets.left,
                 insets.top,
@@ -298,6 +321,17 @@
     }
 
     private void updateWindowHeight() {
+        if (Flags.statusBarStopUpdatingWindowHeight()) {
+            return;
+        }
         mStatusBarWindowController.refreshStatusBarHeight();
     }
+
+    interface HasCornerCutoutFetcher {
+        boolean fetchHasCornerCutout();
+    }
+
+    interface InsetsFetcher {
+        Insets fetchInsets();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 468a3c3..456265b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -73,6 +73,7 @@
     private val configurationController: ConfigurationController,
     private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
     private val darkIconDispatcher: DarkIconDispatcher,
+    private val statusBarContentInsetsProvider: StatusBarContentInsetsProvider,
 ) : ViewController<PhoneStatusBarView>(view) {
 
     private lateinit var battery: BatteryMeterView
@@ -155,7 +156,14 @@
     }
 
     init {
+        // These should likely be done in `onInit`, not `init`.
         mView.setTouchEventHandler(PhoneStatusBarViewTouchHandler())
+        mView.setHasCornerCutoutFetcher {
+            statusBarContentInsetsProvider.currentRotationHasCornerCutout()
+        }
+        mView.setInsetsFetcher {
+            statusBarContentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()
+        }
         mView.init(userChipViewModel)
     }
 
@@ -310,6 +318,7 @@
         private val configurationController: ConfigurationController,
         private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
         private val darkIconDispatcher: DarkIconDispatcher,
+        private val statusBarContentInsetsProvider: StatusBarContentInsetsProvider,
     ) {
         fun create(view: PhoneStatusBarView): PhoneStatusBarViewController {
             val statusBarMoveFromCenterAnimationController =
@@ -335,6 +344,7 @@
                 configurationController,
                 statusOverlayHoverListenerFactory,
                 darkIconDispatcher,
+                statusBarContentInsetsProvider,
             )
         }
     }
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 5486abb..de4d14d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -1007,7 +1007,9 @@
             mKeyguardMessageAreaController.setIsVisible(isShowingAlternateBouncer);
             mKeyguardMessageAreaController.setMessage("");
         }
-        mKeyguardUpdateManager.setAlternateBouncerShowing(isShowingAlternateBouncer);
+        if (!SceneContainerFlag.isEnabled()) {
+            mKeyguardUpdateManager.setAlternateBouncerShowing(isShowingAlternateBouncer);
+        }
 
         if (updateScrim) {
             mCentralSurfaces.updateScrimController();
@@ -1449,10 +1451,13 @@
             mNotificationShadeWindowController.setBouncerShowing(primaryBouncerShowing);
             mCentralSurfaces.setBouncerShowing(primaryBouncerShowing);
         }
-        if (primaryBouncerIsOrWillBeShowing != mLastPrimaryBouncerIsOrWillBeShowing || mFirstUpdate
-                || isPrimaryBouncerShowingChanged) {
-            mKeyguardUpdateManager.sendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing,
-                    primaryBouncerShowing);
+        if (!SceneContainerFlag.isEnabled()) {
+            if (primaryBouncerIsOrWillBeShowing != mLastPrimaryBouncerIsOrWillBeShowing
+                    || mFirstUpdate
+                    || isPrimaryBouncerShowingChanged) {
+                mKeyguardUpdateManager.sendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing,
+                        primaryBouncerShowing);
+            }
         }
 
         mFirstUpdate = false;
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..4f7749b 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
@@ -57,6 +57,7 @@
     private val activityStarter: ActivityStarter,
     // Using a provider to avoid a circular dependency.
     private val viewModel: Provider<ModesDialogViewModel>,
+    private val dialogEventLogger: ModesDialogEventLogger,
     @Main private val mainCoroutineContext: CoroutineContext,
 ) : SystemUIDialog.Delegate {
     // NOTE: This should only be accessed/written from the main thread.
@@ -102,7 +103,9 @@
         )
     }
 
-    private fun openSettings(dialog: SystemUIDialog) {
+    @VisibleForTesting
+    fun openSettings(dialog: SystemUIDialog) {
+        dialogEventLogger.logDialogSettings()
         val animationController =
             dialogTransitionAnimator.createActivityTransitionController(dialog)
         if (animationController == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt
new file mode 100644
index 0000000..33ed419
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.internal.logging.UiEventLogger
+import com.android.settingslib.notification.modes.ZenMode
+import com.android.systemui.qs.QSModesEvent
+import javax.inject.Inject
+
+class ModesDialogEventLogger
+@Inject
+constructor(
+    private val uiEventLogger: UiEventLogger,
+) {
+
+    fun logModeOn(mode: ZenMode) {
+        val id =
+            if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_ON else QSModesEvent.QS_MODES_MODE_ON
+        uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName)
+    }
+
+    fun logModeOff(mode: ZenMode) {
+        val id =
+            if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_OFF else QSModesEvent.QS_MODES_MODE_OFF
+        uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName)
+    }
+
+    fun logModeSettings(mode: ZenMode) {
+        val id =
+            if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_SETTINGS
+            else QSModesEvent.QS_MODES_MODE_SETTINGS
+        uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName)
+    }
+
+    fun logOpenDurationDialog(mode: ZenMode) {
+        // should only occur for manual Do Not Disturb.
+        if (!mode.isManualDnd) {
+            return
+        }
+        uiEventLogger.log(QSModesEvent.QS_MODES_DURATION_DIALOG)
+    }
+
+    fun logDialogSettings() {
+        uiEventLogger.log(QSModesEvent.QS_MODES_SETTINGS)
+    }
+}
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 44b692f..5772099 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
@@ -30,6 +30,7 @@
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
 import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
+import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogEventLogger
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
@@ -49,6 +50,7 @@
     zenModeInteractor: ZenModeInteractor,
     @Background val bgDispatcher: CoroutineDispatcher,
     private val dialogDelegate: ModesDialogDelegate,
+    private val dialogEventLogger: ModesDialogEventLogger,
 ) {
     private val zenDialogMetricsLogger = QSZenModeDialogMetricsLogger(context)
 
@@ -94,14 +96,17 @@
                             if (!mode.rule.isEnabled) {
                                 openSettings(mode)
                             } else if (mode.isActive) {
+                                dialogEventLogger.logModeOff(mode)
                                 zenModeInteractor.deactivateMode(mode)
                             } else {
                                 if (mode.rule.isManualInvocationAllowed) {
                                     if (zenModeInteractor.shouldAskForZenDuration(mode)) {
+                                        dialogEventLogger.logOpenDurationDialog(mode)
                                         // NOTE: The dialog handles turning on the mode itself.
                                         val dialog = makeZenModeDialog()
                                         dialog.show()
                                     } else {
+                                        dialogEventLogger.logModeOn(mode)
                                         zenModeInteractor.activateMode(mode)
                                     }
                                 }
@@ -114,6 +119,7 @@
             .flowOn(bgDispatcher)
 
     private fun openSettings(mode: ZenMode) {
+        dialogEventLogger.logModeSettings(mode)
         val intent: Intent =
             Intent(ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
                 .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, mode.id)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
index 154737c..4f77cd0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
@@ -26,7 +26,6 @@
 import com.android.settingslib.media.MediaDevice.MediaDeviceType
 import com.android.settingslib.media.PhoneMediaDevice
 import com.android.settingslib.volume.data.repository.AudioRepository
-import com.android.settingslib.volume.data.repository.AudioSharingRepository
 import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -37,7 +36,6 @@
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.flatMapLatest
@@ -60,7 +58,6 @@
     private val bluetoothAdapter: BluetoothAdapter?,
     private val deviceIconInteractor: DeviceIconInteractor,
     private val mediaOutputInteractor: MediaOutputInteractor,
-    audioSharingRepository: AudioSharingRepository,
 ) {
 
     val currentAudioDevice: StateFlow<AudioOutputDevice> =
@@ -80,9 +77,6 @@
             .flowOn(backgroundCoroutineContext)
             .stateIn(scope, SharingStarted.Eagerly, AudioOutputDevice.Unknown)
 
-    /** Whether the device is in audio sharing */
-    val isInAudioSharing: Flow<Boolean> = audioSharingRepository.inAudioSharing
-
     private fun AudioDeviceInfo.toAudioOutputDevice(): AudioOutputDevice {
         if (
             BluetoothAdapter.checkBluetoothAddress(address) &&
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
index 2170c36..9aed8ab 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
@@ -36,11 +36,15 @@
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
 interface AudioSharingInteractor {
+    /** Audio sharing state on the device. */
+    val isInAudioSharing: Flow<Boolean>
+
     /** Audio sharing secondary headset volume changes. */
     val volume: Flow<Int?>
 
@@ -76,6 +80,7 @@
     private val audioVolumeInteractor: AudioVolumeInteractor,
     private val audioSharingRepository: AudioSharingRepository
 ) : AudioSharingInteractor {
+    override val isInAudioSharing: Flow<Boolean> = audioSharingRepository.inAudioSharing
 
     override val volume: Flow<Int?> =
         combine(audioSharingRepository.secondaryGroupId, audioSharingRepository.volumeMap) {
@@ -125,13 +130,13 @@
     }
 
     private companion object {
-        const val TAG = "AudioSharingInteractor"
         const val DEFAULT_VOLUME = 20
     }
 }
 
 @SysUISingleton
 class AudioSharingInteractorEmptyImpl @Inject constructor() : AudioSharingInteractor {
+    override val isInAudioSharing: Flow<Boolean> = flowOf(false)
     override val volume: Flow<Int?> = emptyFlow()
     override val volumeMin: Int = EMPTY_VOLUME
     override val volumeMax: Int = EMPTY_VOLUME
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
index ed25129..a270d5f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
@@ -18,6 +18,7 @@
 
 import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
 import com.android.systemui.volume.domain.interactor.AudioOutputInteractor
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
 import com.android.systemui.volume.domain.model.AudioOutputDevice
 import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaOutputComponentModel
 import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlaybackState
@@ -49,11 +50,12 @@
     private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
     audioOutputInteractor: AudioOutputInteractor,
     audioModeInteractor: AudioModeInteractor,
-    interactor: MediaOutputInteractor,
+    mediaOutputInteractor: MediaOutputInteractor,
+    audioSharingInteractor: AudioSharingInteractor,
 ) {
 
     private val sessionWithPlaybackState: StateFlow<Result<SessionWithPlaybackState?>> =
-        interactor.defaultActiveMediaSession
+        mediaOutputInteractor.defaultActiveMediaSession
             .filterData()
             .flatMapLatest { session ->
                 if (session == null) {
@@ -77,7 +79,7 @@
     val mediaOutputModel: StateFlow<Result<MediaOutputComponentModel>> =
         audioModeInteractor.isOngoingCall
             .flatMapLatest { isOngoingCall ->
-                audioOutputInteractor.isInAudioSharing.flatMapLatest { isInAudioSharing ->
+                audioSharingInteractor.isInAudioSharing.flatMapLatest { isInAudioSharing ->
                     if (isOngoingCall) {
                         currentAudioDevice.map {
                             MediaOutputComponentModel.Calling(it, isInAudioSharing)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 075d8ae..7aa415b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -102,6 +102,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.flag.junit.FlagsParameterization;
 import android.service.dreams.IDreamManager;
 import android.service.trust.TrustAgentService;
 import android.telephony.ServiceState;
@@ -111,9 +112,9 @@
 import android.testing.TestableLooper;
 import android.text.TextUtils;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.compose.animation.scene.ObservableTransitionState;
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.foldables.FoldGracePeriodProvider;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -129,6 +130,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig;
 import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigImpl;
@@ -138,9 +140,13 @@
 import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus;
 import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.SceneContainerFlagParameterizationKt;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Scenes;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -149,6 +155,7 @@
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.GlobalSettings;
 
 import org.junit.After;
@@ -175,8 +182,11 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
 @TestableLooper.RunWithLooper
 public class KeyguardUpdateMonitorTest extends SysuiTestCase {
     private static final String PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY =
@@ -277,6 +287,12 @@
     private SelectedUserInteractor mSelectedUserInteractor;
     @Mock
     private DeviceEntryFaceAuthInteractor mFaceAuthInteractor;
+    @Mock
+    private AlternateBouncerInteractor mAlternateBouncerInteractor;
+    @Mock
+    private JavaAdapter mJavaAdapter;
+    @Mock
+    private SceneInteractor mSceneInteractor;
     @Captor
     private ArgumentCaptor<FaceAuthenticationListener> mFaceAuthenticationListener;
 
@@ -301,6 +317,16 @@
             mFingerprintAuthenticatorsRegisteredCallback;
     private final InstanceId mKeyguardInstanceId = InstanceId.fakeInstanceId(999);
 
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getParams() {
+        return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag();
+    }
+
+    public KeyguardUpdateMonitorTest(FlagsParameterization flags) {
+        super();
+        mSetFlagsRule.setFlagsParameterization(flags);
+    }
+
     @Before
     public void setup() throws RemoteException {
         mKosmos = new KosmosJavaAdapter(this);
@@ -993,7 +1019,7 @@
         verifyFingerprintAuthenticateNeverCalled();
         // WHEN alternate bouncer is shown
         mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
-        mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
+        mKeyguardUpdateMonitor.setAlternateBouncerVisibility(true);
 
         // THEN make sure FP listening begins
         verifyFingerprintAuthenticateCall();
@@ -1489,7 +1515,7 @@
     @Test
     public void testShouldNotListenForUdfps_whenInLockDown() {
         // GIVEN a "we should listen for udfps" state
-        setKeyguardBouncerVisibility(false /* isVisible */);
+        mKeyguardUpdateMonitor.setPrimaryBouncerVisibility(false /* isVisible */);
         mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
         when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
 
@@ -2124,7 +2150,7 @@
         verifyFingerprintAuthenticateNeverCalled();
 
         mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
-        mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
+        mKeyguardUpdateMonitor.setAlternateBouncerVisibility(true);
 
         verifyFingerprintAuthenticateCall();
     }
@@ -2323,12 +2349,7 @@
     }
 
     private void bouncerFullyVisible() {
-        setKeyguardBouncerVisibility(true);
-    }
-
-    private void setKeyguardBouncerVisibility(boolean isVisible) {
-        mKeyguardUpdateMonitor.sendPrimaryBouncerChanged(isVisible, isVisible);
-        mTestableLooper.processAllMessages();
+        mKeyguardUpdateMonitor.setPrimaryBouncerVisibility(true);
     }
 
     private void setBroadcastReceiverPendingResult(BroadcastReceiver receiver) {
@@ -2434,7 +2455,12 @@
                     mPackageManager, mFingerprintManager, mBiometricManager,
                     mFaceWakeUpTriggersConfig, mDevicePostureController,
                     Optional.of(mInteractiveToAuthProvider),
-                    mTaskStackChangeListeners, mSelectedUserInteractor, mActivityTaskManager);
+                    mTaskStackChangeListeners, mSelectedUserInteractor, mActivityTaskManager,
+                    () -> mAlternateBouncerInteractor,
+                    () -> mJavaAdapter,
+                    () -> mSceneInteractor);
+            setAlternateBouncerVisibility(false);
+            setPrimaryBouncerVisibility(false);
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
             start();
         }
@@ -2458,5 +2484,25 @@
         protected int getBiometricLockoutDelay() {
             return 0;
         }
+
+        private void setPrimaryBouncerVisibility(boolean isVisible) {
+            if (SceneContainerFlag.isEnabled()) {
+                ObservableTransitionState transitionState = new ObservableTransitionState.Idle(
+                        isVisible ? Scenes.Bouncer : Scenes.Lockscreen);
+                when(mSceneInteractor.getTransitionState()).thenReturn(
+                        MutableStateFlow(transitionState));
+                onTransitionStateChanged(transitionState);
+            } else {
+                sendPrimaryBouncerChanged(isVisible, isVisible);
+                mTestableLooper.processAllMessages();
+            }
+        }
+
+        private void setAlternateBouncerVisibility(boolean isVisible) {
+            if (SceneContainerFlag.isEnabled()) {
+                when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(isVisible);
+            }
+            onAlternateBouncerVisibilityChange(isVisible);
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
index 206bbbf..4ce2d7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
@@ -51,6 +51,7 @@
 
 import androidx.compose.ui.platform.ComposeView;
 import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
@@ -611,7 +612,8 @@
         when(mQSContainerImplController.getView()).thenReturn(mContainer);
         when(mQSPanelController.getTileLayout()).thenReturn(mQQsTileLayout);
         when(mQuickQSPanelController.getTileLayout()).thenReturn(mQsTileLayout);
-        when(mFooterActionsViewModelFactory.create(any())).thenReturn(mFooterActionsViewModel);
+        when(mFooterActionsViewModelFactory.create(any(LifecycleOwner.class)))
+                .thenReturn(mFooterActionsViewModel);
     }
 
     private void setUpMedia() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 30e7247..70ac31d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -391,6 +391,7 @@
                 configurationController,
                 mStatusOverlayHoverListenerFactory,
                 fakeDarkIconDispatcher,
+                mock(StatusBarContentInsetsProvider::class.java),
             )
             .create(view)
             .also { it.init() }
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 ed5ec7b2..648ddf8 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
@@ -32,6 +32,7 @@
 import android.view.WindowInsets
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT
 import com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.SysuiTestCase
@@ -42,6 +43,7 @@
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
+import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
@@ -54,21 +56,14 @@
     private val systemIconsContainer: View
         get() = view.requireViewById(R.id.system_icons)
 
-    private val contentInsetsProvider = mock<StatusBarContentInsetsProvider>()
     private val windowController = mock<StatusBarWindowController>()
 
     @Before
     fun setUp() {
-        mDependency.injectTestDependency(
-            StatusBarContentInsetsProvider::class.java,
-            contentInsetsProvider
-        )
         mDependency.injectTestDependency(StatusBarWindowController::class.java, windowController)
         context.ensureTestableResources()
         view = spy(createStatusBarView())
         whenever(view.rootWindowInsets).thenReturn(emptyWindowInsets())
-        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
-            .thenReturn(Insets.NONE)
     }
 
     @Test
@@ -183,21 +178,40 @@
     }
 
     @Test
-    fun onAttachedToWindow_updatesWindowHeight() {
+    @DisableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
+    fun onAttachedToWindow_flagOff_updatesWindowHeight() {
         view.onAttachedToWindow()
 
         verify(windowController).refreshStatusBarHeight()
     }
 
     @Test
-    fun onConfigurationChanged_updatesWindowHeight() {
+    @EnableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
+    fun onAttachedToWindow_flagOn_doesNotUpdateWindowHeight() {
+        view.onAttachedToWindow()
+
+        verify(windowController, never()).refreshStatusBarHeight()
+    }
+
+    @Test
+    @DisableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
+    fun onConfigurationChanged_flagOff_updatesWindowHeight() {
         view.onConfigurationChanged(Configuration())
 
         verify(windowController).refreshStatusBarHeight()
     }
 
     @Test
-    fun onConfigurationChanged_multipleCalls_updatesWindowHeightMultipleTimes() {
+    @EnableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
+    fun onConfigurationChanged_flagOn_doesNotUpdateWindowHeight() {
+        view.onConfigurationChanged(Configuration())
+
+        verify(windowController, never()).refreshStatusBarHeight()
+    }
+
+    @Test
+    @DisableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
+    fun onConfigurationChanged_multipleCalls_flagOff_updatesWindowHeightMultipleTimes() {
         view.onConfigurationChanged(Configuration())
         view.onConfigurationChanged(Configuration())
         view.onConfigurationChanged(Configuration())
@@ -207,10 +221,20 @@
     }
 
     @Test
+    @EnableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
+    fun onConfigurationChanged_multipleCalls_flagOn_neverUpdatesWindowHeight() {
+        view.onConfigurationChanged(Configuration())
+        view.onConfigurationChanged(Configuration())
+        view.onConfigurationChanged(Configuration())
+        view.onConfigurationChanged(Configuration())
+
+        verify(windowController, never()).refreshStatusBarHeight()
+    }
+
+    @Test
     fun onAttachedToWindow_updatesLeftTopRightPaddingsBasedOnInsets() {
         val insets = Insets.of(/* left= */ 10, /* top= */ 20, /* right= */ 30, /* bottom= */ 40)
-        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
-            .thenReturn(insets)
+        view.setInsetsFetcher { insets }
 
         view.onAttachedToWindow()
 
@@ -221,10 +245,23 @@
     }
 
     @Test
+    fun onAttachedToWindow_noInsetsFetcher_noCrash() {
+        // Don't call `PhoneStatusBarView.setInsetsFetcher`
+
+        // WHEN the view is attached
+        view.onAttachedToWindow()
+
+        // THEN there's no crash, and the padding stays as it was
+        assertThat(view.paddingLeft).isEqualTo(0)
+        assertThat(view.paddingTop).isEqualTo(0)
+        assertThat(view.paddingRight).isEqualTo(0)
+        assertThat(view.paddingBottom).isEqualTo(0)
+    }
+
+    @Test
     fun onConfigurationChanged_updatesLeftTopRightPaddingsBasedOnInsets() {
         val insets = Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
-        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
-            .thenReturn(insets)
+        view.setInsetsFetcher { insets }
 
         view.onConfigurationChanged(Configuration())
 
@@ -235,17 +272,31 @@
     }
 
     @Test
+    fun onConfigurationChanged_noInsetsFetcher_noCrash() {
+        // Don't call `PhoneStatusBarView.setInsetsFetcher`
+
+        // WHEN the view is attached
+        view.onConfigurationChanged(Configuration())
+
+        // THEN there's no crash, and the padding stays as it was
+        assertThat(view.paddingLeft).isEqualTo(0)
+        assertThat(view.paddingTop).isEqualTo(0)
+        assertThat(view.paddingRight).isEqualTo(0)
+        assertThat(view.paddingBottom).isEqualTo(0)
+    }
+
+    @Test
     fun onConfigurationChanged_noRelevantChange_doesNotUpdateInsets() {
         val previousInsets =
             Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
-        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
-            .thenReturn(previousInsets)
+        view.setInsetsFetcher { previousInsets }
+
         context.orCreateTestableResources.overrideConfiguration(Configuration())
         view.onAttachedToWindow()
 
         val newInsets = Insets.NONE
-        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
-            .thenReturn(newInsets)
+        view.setInsetsFetcher { newInsets }
+
         view.onConfigurationChanged(Configuration())
 
         assertThat(view.paddingLeft).isEqualTo(previousInsets.left)
@@ -258,16 +309,14 @@
     fun onConfigurationChanged_densityChanged_updatesInsets() {
         val previousInsets =
             Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
-        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
-            .thenReturn(previousInsets)
+        view.setInsetsFetcher { previousInsets }
         val configuration = Configuration()
         configuration.densityDpi = 123
         context.orCreateTestableResources.overrideConfiguration(configuration)
         view.onAttachedToWindow()
 
         val newInsets = Insets.NONE
-        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
-            .thenReturn(newInsets)
+        view.setInsetsFetcher { newInsets }
         configuration.densityDpi = 456
         view.onConfigurationChanged(configuration)
 
@@ -281,16 +330,14 @@
     fun onConfigurationChanged_fontScaleChanged_updatesInsets() {
         val previousInsets =
             Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
-        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
-            .thenReturn(previousInsets)
+        view.setInsetsFetcher { previousInsets }
         val configuration = Configuration()
         configuration.fontScale = 1f
         context.orCreateTestableResources.overrideConfiguration(configuration)
         view.onAttachedToWindow()
 
         val newInsets = Insets.NONE
-        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
-            .thenReturn(newInsets)
+        view.setInsetsFetcher { newInsets }
         configuration.fontScale = 2f
         view.onConfigurationChanged(configuration)
 
@@ -316,8 +363,7 @@
     @Test
     fun onApplyWindowInsets_updatesLeftTopRightPaddingsBasedOnInsets() {
         val insets = Insets.of(/* left= */ 90, /* top= */ 10, /* right= */ 45, /* bottom= */ 50)
-        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
-            .thenReturn(insets)
+        view.setInsetsFetcher { insets }
 
         view.onApplyWindowInsets(WindowInsets(Rect()))
 
@@ -358,7 +404,7 @@
             /* typeVisibilityMap = */ booleanArrayOf(),
             /* isRound = */ false,
             /* forceConsumingTypes = */ 0,
-            /* forceConsumingCaptionBar = */ false,
+            /* forceConsumingOpaqueCaptionBar = */ false,
             /* suppressScrimTypes = */ 0,
             /* displayCutout = */ DisplayCutout.NO_CUTOUT,
             /* roundedCorners = */ RoundedCorners.NO_ROUNDED_CORNERS,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
index bf0a39b..06b3b57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
@@ -57,6 +57,7 @@
     private val activityStarter = kosmos.activityStarter
     private val mockDialogTransitionAnimator = kosmos.mockDialogTransitionAnimator
     private val mockAnimationController = kosmos.mockActivityTransitionAnimatorController
+    private val mockDialogEventLogger = kosmos.mockModesDialogEventLogger
     private lateinit var underTest: ModesDialogDelegate
 
     @Before
@@ -75,6 +76,7 @@
                 mockDialogTransitionAnimator,
                 activityStarter,
                 { kosmos.modesDialogViewModel },
+                mockDialogEventLogger,
                 kosmos.mainCoroutineContext,
             )
     }
@@ -121,4 +123,12 @@
 
         assertThat(underTest.currentDialog).isNull()
     }
+
+    @Test
+    fun openSettings_logsEvent() =
+        testScope.runTest {
+            val dialog: SystemUIDialog = mock()
+            underTest.openSettings(dialog)
+            verify(mockDialogEventLogger).logDialogSettings()
+        }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt
index ee48c10..2ab8221 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.communal.domain.interactor
 
 import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.communal.shared.log.communalSceneLogger
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 
@@ -24,6 +25,7 @@
     Kosmos.Fixture {
         CommunalSceneInteractor(
             applicationScope = applicationCoroutineScope,
-            communalSceneRepository = communalSceneRepository,
+            repository = communalSceneRepository,
+            logger = communalSceneLogger,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/log/CommunalSceneLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/log/CommunalSceneLoggerKosmos.kt
new file mode 100644
index 0000000..b560ee8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/log/CommunalSceneLoggerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.communal.shared.log
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+
+val Kosmos.communalSceneLogger: CommunalSceneLogger by
+    Kosmos.Fixture { CommunalSceneLogger(logcatLogBuffer("CommunalSceneLogger")) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
index f162594..64ae051 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
@@ -38,6 +39,7 @@
             mainDispatcher = testDispatcher,
             keyguardInteractor = keyguardInteractor,
             glanceableHubTransitions = glanceableHubTransitions,
+            communalSceneInteractor = communalSceneInteractor,
             communalSettingsInteractor = communalSettingsInteractor,
             powerInteractor = powerInteractor,
             keyguardOcclusionInteractor = keyguardOcclusionInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
index 450dcc2..d06bab2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
@@ -19,13 +19,11 @@
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 @ExperimentalCoroutinesApi
 val Kosmos.dreamingToLockscreenTransitionViewModel by Fixture {
     DreamingToLockscreenTransitionViewModel(
-        fromDreamingTransitionInteractor = mock(),
         animationFlow = keyguardTransitionAnimationFlow,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
index 9ff7dd5..ffe6918 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
@@ -27,6 +27,9 @@
     numRunningPackages: Int = 0,
 ) : FgsManagerController {
 
+    var initialized = false
+        private set
+
     override var numRunningPackages = numRunningPackages
         set(value) {
             if (value != field) {
@@ -53,7 +56,9 @@
         dialogDismissedListeners.forEach { it.onDialogDismissed() }
     }
 
-    override fun init() {}
+    override fun init() {
+        initialized = true
+    }
 
     override fun showDialog(expandable: Expandable?) {}
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
new file mode 100644
index 0000000..d37d8f3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment.viewmodel
+
+import android.content.res.mainResources
+import androidx.lifecycle.LifecycleCoroutineScope
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.footerActionsController
+import com.android.systemui.qs.footerActionsViewModelFactory
+import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModel
+import com.android.systemui.shade.largeScreenHeaderHelper
+import com.android.systemui.shade.transition.largeScreenShadeInterpolator
+import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
+import com.android.systemui.statusbar.phone.keyguardBypassController
+import com.android.systemui.statusbar.sysuiStatusBarStateController
+
+val Kosmos.qsFragmentComposeViewModelFactory by
+    Kosmos.Fixture {
+        object : QSFragmentComposeViewModel.Factory {
+            override fun create(
+                lifecycleScope: LifecycleCoroutineScope
+            ): QSFragmentComposeViewModel {
+                return QSFragmentComposeViewModel(
+                    quickSettingsContainerViewModel,
+                    mainResources,
+                    footerActionsViewModelFactory,
+                    footerActionsController,
+                    sysuiStatusBarStateController,
+                    keyguardBypassController,
+                    disableFlagsRepository,
+                    largeScreenShadeInterpolator,
+                    configurationInteractor,
+                    largeScreenHeaderHelper,
+                    lifecycleScope,
+                )
+            }
+        }
+    }
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
index 99bb479..932e768 100644
--- 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
@@ -33,6 +33,7 @@
             dialogTransitionAnimator,
             activityStarter,
             { modesDialogViewModel },
+            modesDialogEventLogger,
             mainCoroutineContext,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerKosmos.kt
new file mode 100644
index 0000000..24e7a87
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.internal.logging.uiEventLogger
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+var Kosmos.modesDialogEventLogger by Kosmos.Fixture { ModesDialogEventLogger(uiEventLogger) }
+var Kosmos.mockModesDialogEventLogger by Kosmos.Fixture { mock<ModesDialogEventLogger>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt
new file mode 100644
index 0000000..5146f77
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt
@@ -0,0 +1,116 @@
+/*
+ * 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 android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.QSModesEvent
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class ModesDialogEventLoggerTest : SysuiTestCase() {
+
+    private val uiEventLogger = UiEventLoggerFake()
+    private val underTest = ModesDialogEventLogger(uiEventLogger)
+
+    @Test
+    fun testLogModeOn_manual() {
+        underTest.logModeOn(TestModeBuilder.MANUAL_DND_INACTIVE)
+
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        uiEventLogger[0].match(QSModesEvent.QS_MODES_DND_ON, "android")
+    }
+
+    @Test
+    fun testLogModeOff_manual() {
+        underTest.logModeOff(TestModeBuilder.MANUAL_DND_ACTIVE)
+
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        uiEventLogger[0].match(QSModesEvent.QS_MODES_DND_OFF, "android")
+    }
+
+    @Test
+    fun testLogModeSettings_manual() {
+        underTest.logModeSettings(TestModeBuilder.MANUAL_DND_ACTIVE)
+
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        uiEventLogger[0].match(QSModesEvent.QS_MODES_DND_SETTINGS, "android")
+    }
+
+    @Test
+    fun testLogModeOn_automatic() {
+        underTest.logModeOn(TestModeBuilder().setActive(true).setPackage("pkg1").build())
+
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        uiEventLogger[0].match(QSModesEvent.QS_MODES_MODE_ON, "pkg1")
+    }
+
+    @Test
+    fun testLogModeOff_automatic() {
+        underTest.logModeOff(TestModeBuilder().setActive(false).setPackage("pkg2").build())
+
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        uiEventLogger[0].match(QSModesEvent.QS_MODES_MODE_OFF, "pkg2")
+    }
+
+    @Test
+    fun testLogModeSettings_automatic() {
+        underTest.logModeSettings(TestModeBuilder().setPackage("pkg3").build())
+
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        uiEventLogger[0].match(QSModesEvent.QS_MODES_MODE_SETTINGS, "pkg3")
+    }
+
+    @Test
+    fun testLogOpenDurationDialog_manual() {
+        underTest.logOpenDurationDialog(TestModeBuilder.MANUAL_DND_INACTIVE)
+
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        // package not logged for duration dialog as it only applies to manual mode
+        uiEventLogger[0].match(QSModesEvent.QS_MODES_DURATION_DIALOG, null)
+    }
+
+    @Test
+    fun testLogOpenDurationDialog_automatic_doesNotLog() {
+        underTest.logOpenDurationDialog(
+            TestModeBuilder().setActive(false).setPackage("mypkg").build()
+        )
+
+        // ignore calls to open dialog on something other than the manual rule (shouldn't happen)
+        assertThat(uiEventLogger.numLogs()).isEqualTo(0)
+    }
+
+    @Test
+    fun testLogDialogSettings() {
+        underTest.logDialogSettings()
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        uiEventLogger[0].match(QSModesEvent.QS_MODES_SETTINGS, null)
+    }
+
+    private fun UiEventLoggerFake.FakeUiEvent.match(event: QSModesEvent, modePackage: String?) {
+        assertThat(eventId).isEqualTo(event.id)
+        assertThat(packageName).isEqualTo(modePackage)
+    }
+}
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
index 00020f8..3571a73 100644
--- 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
@@ -21,6 +21,7 @@
 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 com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger
 import javax.inject.Provider
 
 val Kosmos.modesDialogViewModel: ModesDialogViewModel by
@@ -30,5 +31,6 @@
             zenModeInteractor,
             testDispatcher,
             Provider { modesDialogDelegate }.get(),
+            modesDialogEventLogger,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
index 295e150..2e1ecfd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
@@ -219,7 +219,7 @@
  *
  * NOTE: this uses the KotlinArgumentCaptor to avoid the NullPointerException.
  */
-@Deprecated("Replace with mockito-kotlin", level = WARNING)
+// TODO(359670968): rewrite this to use mockito-kotlin
 inline fun <reified T : Any> withArgCaptor(block: KotlinArgumentCaptor<T>.() -> Unit): T =
     kotlinArgumentCaptor<T>().apply { block() }.value
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/OWNERS b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/OWNERS
new file mode 100644
index 0000000..1f07df9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/OWNERS
@@ -0,0 +1 @@
+include /packages/SystemUI/src/com/android/systemui/volume/OWNERS
\ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt
index e2d414e..3ac565a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt
@@ -22,7 +22,6 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.volume.data.repository.audioRepository
-import com.android.systemui.volume.data.repository.audioSharingRepository
 import com.android.systemui.volume.mediaOutputInteractor
 
 val Kosmos.audioOutputInteractor by
@@ -37,6 +36,5 @@
             bluetoothAdapter,
             deviceIconInteractor,
             mediaOutputInteractor,
-            audioSharingRepository,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt
index 9f11822..63a1325 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.volume.domain.interactor.audioModeInteractor
 import com.android.systemui.volume.domain.interactor.audioOutputInteractor
+import com.android.systemui.volume.domain.interactor.audioSharingInteractor
 import com.android.systemui.volume.mediaDeviceSessionInteractor
 import com.android.systemui.volume.mediaOutputInteractor
 
@@ -31,5 +32,6 @@
             audioOutputInteractor,
             audioModeInteractor,
             mediaOutputInteractor,
+            audioSharingInteractor,
         )
     }
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 2c4bc7c..531fa45 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -78,8 +78,8 @@
 import android.util.Size;
 import android.view.Surface;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl;
 import androidx.camera.extensions.impl.AutoPreviewExtenderImpl;
 import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index b052d23..5c6f99a 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -16,8 +16,6 @@
 
 package com.android.server.accessibility.magnification;
 
-import static android.view.InputDevice.SOURCE_MOUSE;
-import static android.view.InputDevice.SOURCE_STYLUS;
 import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
@@ -342,8 +340,12 @@
                 cancelFling();
             }
             handleTouchEventWith(mCurrentState, event, rawEvent, policyFlags);
-        } else if (Flags.enableMagnificationFollowsMouse()
-                && (event.getSource() == SOURCE_MOUSE || event.getSource() == SOURCE_STYLUS)) {
+        }
+    }
+
+    @Override
+    void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (Flags.enableMagnificationFollowsMouse()) {
             if (mFullScreenMagnificationController.isActivated(mDisplayId)) {
                 // TODO(b/354696546): Allow mouse/stylus to activate whichever display they are
                 // over, rather than only interacting with the current display.
@@ -351,8 +353,6 @@
                 // Send through the mouse/stylus event handler.
                 mMouseEventHandler.onEvent(event, mDisplayId);
             }
-            // Dispatch to normal event handling flow.
-            dispatchTransformedEvent(event, rawEvent, policyFlags);
         }
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
index 08411c2..446123f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
@@ -127,49 +127,41 @@
         if (DEBUG_EVENT_STREAM) {
             storeEventInto(mDebugInputEventHistory, event);
         }
-        if (shouldDispatchTransformedEvent(event)) {
-            dispatchTransformedEvent(event, rawEvent, policyFlags);
-        } else {
-            onMotionEventInternal(event, rawEvent, policyFlags);
+        switch (event.getSource()) {
+            case SOURCE_TOUCHSCREEN: {
+                if (magnificationShortcutExists()) {
+                    // Observe touchscreen events while magnification activation is detected.
+                    onMotionEventInternal(event, rawEvent, policyFlags);
 
-            final int action = event.getAction();
-            if (action == MotionEvent.ACTION_DOWN) {
-                mCallback.onTouchInteractionStart(mDisplayId, getMode());
-            } else if (action == ACTION_UP || action == ACTION_CANCEL) {
-                mCallback.onTouchInteractionEnd(mDisplayId, getMode());
+                    final int action = event.getAction();
+                    if (action == MotionEvent.ACTION_DOWN) {
+                        mCallback.onTouchInteractionStart(mDisplayId, getMode());
+                    } else if (action == ACTION_UP || action == ACTION_CANCEL) {
+                        mCallback.onTouchInteractionEnd(mDisplayId, getMode());
+                    }
+                    // Return early: Do not dispatch event through normal eventing
+                    // flow, it has been fully consumed by the magnifier.
+                    return;
+                }
+            } break;
+            case SOURCE_MOUSE:
+            case SOURCE_STYLUS: {
+                if (magnificationShortcutExists() && Flags.enableMagnificationFollowsMouse()) {
+                    handleMouseOrStylusEvent(event, rawEvent, policyFlags);
+                }
             }
+                break;
+            default:
+                break;
         }
+        // Dispatch event through normal eventing flow.
+        dispatchTransformedEvent(event, rawEvent, policyFlags);
     }
 
-    /**
-     * Some touchscreen, mouse and stylus events may modify magnifier state. Checks for whether the
-     * event should not be dispatched to the magnifier.
-     *
-     * @param event The event to check.
-     * @return `true` if the event should be sent through the normal event flow or `false` if it
-     *     should be observed by magnifier.
-     */
-    private boolean shouldDispatchTransformedEvent(MotionEvent event) {
-        if (event.getSource() == SOURCE_TOUCHSCREEN) {
-            if (mDetectSingleFingerTripleTap
-                    || mDetectTwoFingerTripleTap
-                    || mDetectShortcutTrigger) {
-                // Observe touchscreen events while magnification activation is detected.
-                return false;
-            }
-        }
-        if (Flags.enableMagnificationFollowsMouse()) {
-            if (event.isFromSource(SOURCE_MOUSE) || event.isFromSource(SOURCE_STYLUS)) {
-                // Note that mouse events include other mouse-like pointing devices
-                // such as touchpads and pointing sticks.
-                // Observe any mouse or stylus movement.
-                // We observe all movement to ensure that events continue to come in order,
-                // even though only some movement types actually move the viewport.
-                return false;
-            }
-        }
-        // Magnification dispatches (ignores) all other events
-        return true;
+    private boolean magnificationShortcutExists() {
+        return (mDetectSingleFingerTripleTap
+                || mDetectTwoFingerTripleTap
+                || mDetectShortcutTrigger);
     }
 
     final void dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
@@ -202,6 +194,13 @@
     abstract void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags);
 
     /**
+     * Called when this MagnificationGestureHandler should handle a mouse or stylus motion event,
+     * but not re-dispatch it when completed.
+     */
+    abstract void handleMouseOrStylusEvent(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags);
+
+    /**
      * Called when the shortcut target is magnification.
      */
     public void notifyShortcutTriggered() {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index 1818cdd..a841404 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -147,9 +147,13 @@
     }
 
     @Override
+    void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        // Window Magnification viewport doesn't move with mouse events (yet).
+    }
+
+    @Override
     void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         if (event.getSource() != SOURCE_TOUCHSCREEN) {
-            // Window Magnification viewport doesn't move with mouse events (yet).
             return;
         }
         // To keep InputEventConsistencyVerifiers within GestureDetectors happy.
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 930af5e..5044e93 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -71,6 +71,7 @@
 import android.util.Slog;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
 
 import com.android.internal.util.FrameworkStatsLog;
 
@@ -244,10 +245,18 @@
             Slog.e(TAG, "Failed to start new event because already have active event.");
             return;
         }
+        Slog.d(TAG, "Started new PresentationStatsEvent");
         mEventInternal = Optional.of(new PresentationStatsEventInternal());
     }
 
     /**
+     * Test use only, returns a copy of the events object
+     */
+    Optional<PresentationStatsEventInternal> getInternalEvent() {
+        return mEventInternal;
+    }
+
+    /**
      * Set request_id
      */
     public void maybeSetRequestId(int requestId) {
@@ -339,10 +348,16 @@
         });
     }
 
-    public void maybeSetCountShown(int datasets) {
+    /**
+     * This is called when a dataset is shown to the user. Will set the count shown,
+     * related timestamps and presentation reason.
+     */
+    public void logWhenDatasetShown(int datasets) {
         mEventInternal.ifPresent(
                 event -> {
+                    maybeSetSuggestionPresentedTimestampMs();
                     event.mCountShown = datasets;
+                    event.mNoPresentationReason = NOT_SHOWN_REASON_ANY_SHOWN;
                 });
     }
 
@@ -405,7 +420,12 @@
 
     public void maybeSetDisplayPresentationType(@UiType int uiType) {
         mEventInternal.ifPresent(event -> {
-            event.mDisplayPresentationType = getDisplayPresentationType(uiType);
+            // There are cases in which another UI type will show up after selects a dataset
+            // such as with Inline after Fill Dialog. Set as the first presentation type only.
+            if (event.mDisplayPresentationType
+                    == AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__UNKNOWN_AUTOFILL_DISPLAY_PRESENTATION_TYPE) {
+                event.mDisplayPresentationType = getDisplayPresentationType(uiType);
+            }
         });
     }
 
@@ -430,9 +450,12 @@
     }
 
     public void maybeSetSuggestionSentTimestampMs(int timestamp) {
-        mEventInternal.ifPresent(event -> {
-            event.mSuggestionSentTimestampMs = timestamp;
-        });
+        mEventInternal.ifPresent(
+                event -> {
+                    if (event.mSuggestionSentTimestampMs == DEFAULT_VALUE_INT) {
+                        event.mSuggestionSentTimestampMs = timestamp;
+                    }
+                });
     }
 
     public void maybeSetSuggestionSentTimestampMs() {
@@ -481,8 +504,6 @@
 
     public void maybeSetInlinePresentationAndSuggestionHostUid(Context context, int userId) {
         mEventInternal.ifPresent(event -> {
-            event.mDisplayPresentationType =
-                    AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE;
             String imeString = Settings.Secure.getStringForUser(context.getContentResolver(),
                     Settings.Secure.DEFAULT_INPUT_METHOD, userId);
             if (TextUtils.isEmpty(imeString)) {
@@ -602,40 +623,56 @@
     }
 
     /**
+     * Sets the field length whenever the text changes. Will keep track of the first
+     * and last modification lengths.
+     */
+    public void updateTextFieldLength(AutofillValue value) {
+        mEventInternal.ifPresent(event -> {
+            if (value == null || !value.isText()) {
+                return;
+            }
+
+            int length = value.getTextValue().length();
+
+            if (event.mFieldFirstLength == DEFAULT_VALUE_INT) {
+                event.mFieldFirstLength = length;
+            }
+            event.mFieldLastLength = length;
+        });
+    }
+
+    /**
      * Set various timestamps whenever the ViewState is modified
      *
      * <p>If the ViewState contains ViewState.STATE_AUTOFILLED, sets field_autofilled_timestamp_ms
      * else, set field_first_modified_timestamp_ms (if unset) and field_last_modified_timestamp_ms
      */
-    public void onFieldTextUpdated(ViewState state, int length) {
+    public void onFieldTextUpdated(ViewState state, AutofillValue value) {
         mEventInternal.ifPresent(event -> {
-                    int timestamp = getElapsedTime();
-                    // Focused id should be set before this is called
-                    if (state == null || state.id == null || state.id.getViewId() != event.mFocusedId) {
-                        // if these don't match, the currently field different than before
-                        Slog.w(
-                                TAG,
-                                "Bad view state for: " + event.mFocusedId);
-                        return;
-                    }
+            int timestamp = getElapsedTime();
+            // Focused id should be set before this is called
+            if (state == null || state.id == null || state.id.getViewId() != event.mFocusedId) {
+                // if these don't match, the currently field different than before
+                Slog.w(
+                        TAG,
+                        "Bad view state for: " + event.mFocusedId + ", state: " + state);
+                return;
+            }
 
-                    // Text changed because filling into form, just log Autofill timestamp
-                    if ((state.getState() & ViewState.STATE_AUTOFILLED) != 0) {
-                        event.mAutofilledTimestampMs = timestamp;
-                        return;
-                    }
+            updateTextFieldLength(value);
 
-                    // Set length variables
-                    if (event.mFieldFirstLength == DEFAULT_VALUE_INT) {
-                        event.mFieldFirstLength = length;
-                    }
-                    event.mFieldLastLength = length;
+            // Text changed because filling into form, just log Autofill timestamp
+            if ((state.getState() & ViewState.STATE_AUTOFILLED) != 0) {
+                event.mAutofilledTimestampMs = timestamp;
+                return;
+            }
 
-                    // Set timestamp variables
-                    if (event.mFieldModifiedFirstTimestampMs == DEFAULT_VALUE_INT) {
-                        event.mFieldModifiedFirstTimestampMs = timestamp;
-                    }
-                    event.mFieldModifiedLastTimestampMs = timestamp;
+
+            // Set timestamp variables
+            if (event.mFieldModifiedFirstTimestampMs == DEFAULT_VALUE_INT) {
+                event.mFieldModifiedFirstTimestampMs = timestamp;
+            }
+            event.mFieldModifiedLastTimestampMs = timestamp;
         });
     }
 
@@ -796,7 +833,10 @@
         });
     }
 
-    public void logAndEndEvent() {
+    /**
+     * Finish and log the event.
+     */
+    public void logAndEndEvent(String caller) {
         if (!mEventInternal.isPresent()) {
             Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same "
                     + "event");
@@ -804,7 +844,8 @@
         }
         PresentationStatsEventInternal event = mEventInternal.get();
         if (sVerbose) {
-            Slog.v(TAG, "Log AutofillPresentationEventReported:"
+            Slog.v(TAG, "(" + caller + ") "
+                    + "Log AutofillPresentationEventReported:"
                     + " requestId=" + event.mRequestId
                     + " sessionId=" + mSessionId
                     + " mNoPresentationEventReason=" + event.mNoPresentationReason
@@ -926,7 +967,7 @@
         mEventInternal = Optional.empty();
     }
 
-    private static final class PresentationStatsEventInternal {
+    static final class PresentationStatsEventInternal {
         int mRequestId;
         @NotShownReason int mNoPresentationReason = NOT_SHOWN_REASON_UNKNOWN;
         boolean mIsDatasetAvailable;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 6dea8b0..c75fd0b 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -28,7 +28,6 @@
 import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC;
 import static android.service.autofill.Dataset.PICK_REASON_UNKNOWN;
 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_CREDMAN_BOTTOM_SHEET;
-import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG;
 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE;
 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_UNKNOWN;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
@@ -67,10 +66,10 @@
 import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_SUCCESS;
 import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_TIMEOUT;
 import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_TRANSACTION_TOO_LARGE;
+import static com.android.server.autofill.Helper.SaveInfoStats;
 import static com.android.server.autofill.Helper.containsCharsInOrder;
 import static com.android.server.autofill.Helper.createSanitizers;
 import static com.android.server.autofill.Helper.getNumericValue;
-import static com.android.server.autofill.Helper.SaveInfoStats;
 import static com.android.server.autofill.Helper.sDebug;
 import static com.android.server.autofill.Helper.sVerbose;
 import static com.android.server.autofill.Helper.toArray;
@@ -78,6 +77,7 @@
 import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_RESULT_SUCCESS;
 import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_TYPE_DATASET_AUTHENTICATION;
 import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_TYPE_FULL_AUTHENTICATION;
+import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_ANY_SHOWN;
 import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_NO_FOCUS;
 import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_FAILED;
 import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_TIMEOUT;
@@ -1288,11 +1288,11 @@
      * Clears the existing response for the partition, reads a new structure, and then requests a
      * new fill response from the fill service.
      *
-     * <p> Also asks the IME to make an inline suggestions request if it's enabled.
+     * <p>Also asks the IME to make an inline suggestions request if it's enabled.
      */
     @GuardedBy("mLock")
-    private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState,
-            int flags) {
+    private Optional<Integer> requestNewFillResponseLocked(
+            @NonNull ViewState viewState, int newState, int flags) {
         boolean isSecondary = shouldRequestSecondaryProvider(flags);
         final FillResponse existingResponse = isSecondary
                 ? viewState.getSecondaryResponse() : viewState.getResponse();
@@ -1333,7 +1333,7 @@
             mFillRequestEventLogger.maybeSetIsAugmented(true);
             mFillRequestEventLogger.logAndEndEvent();
             triggerAugmentedAutofillLocked(flags);
-            return;
+            return Optional.empty();
         }
 
         viewState.setState(newState);
@@ -1353,11 +1353,6 @@
                     + ", flags=" + flags);
         }
         boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
-        mPresentationStatsEventLogger.maybeSetRequestId(requestId);
-        mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
-        mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
-                mFieldClassificationIdSnapshot);
-        mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
         mFillRequestEventLogger.maybeSetRequestId(requestId);
         mFillRequestEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
         mSaveEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
@@ -1417,6 +1412,8 @@
 
         // Now request the assist structure data.
         requestAssistStructureLocked(requestId, flags);
+
+        return Optional.of(requestId);
     }
 
     private boolean isRequestSupportFillDialog(int flags) {
@@ -1662,6 +1659,7 @@
         final LogMaker requestLog;
 
         synchronized (mLock) {
+            mPresentationStatsEventLogger.maybeSetRequestId(requestId);
             // Start a new FillResponse logger for the success case.
             mFillResponseEventLogger.startLogForNewResponse();
             mFillResponseEventLogger.maybeSetRequestId(requestId);
@@ -2419,7 +2417,7 @@
                         NOT_SHOWN_REASON_REQUEST_FAILED);
                 mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_FAILURE);
             }
-            mPresentationStatsEventLogger.logAndEndEvent();
+            mPresentationStatsEventLogger.logAndEndEvent("fill request failure");
             mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis();
             mFillResponseEventLogger.logAndEndEvent();
         }
@@ -2642,6 +2640,8 @@
     public void onShown(int uiType, int numDatasetsShown) {
         synchronized (mLock) {
             mPresentationStatsEventLogger.maybeSetDisplayPresentationType(uiType);
+            mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
+                    NOT_SHOWN_REASON_ANY_SHOWN);
 
             if (uiType == UI_TYPE_INLINE) {
                 // Inline Suggestions are inflated one at a time
@@ -2657,7 +2657,7 @@
                 }
                 mLoggedInlineDatasetShown = true;
             } else {
-                mPresentationStatsEventLogger.maybeSetCountShown(numDatasetsShown);
+                mPresentationStatsEventLogger.logWhenDatasetShown(numDatasetsShown);
                 // Explicitly sets maybeSetSuggestionPresentedTimestampMs
                 mPresentationStatsEventLogger.maybeSetSuggestionPresentedTimestampMs();
                 mService.logDatasetShown(this.id, mClientState, uiType);
@@ -2800,7 +2800,10 @@
             if (mCurrentViewId == null) {
                 return;
             }
+            mPresentationStatsEventLogger.logAndEndEvent("fallback from fill dialog");
+            startNewEventForPresentationStatsEventLogger();
             final ViewState currentView = mViewStates.get(mCurrentViewId);
+            logPresentationStatsOnViewEnteredLocked(currentView.getResponse(), false);
             currentView.maybeCallOnFillReady(mFlags);
         }
     }
@@ -2850,7 +2853,7 @@
         if (requestId == AUGMENTED_AUTOFILL_REQUEST_ID) {
             setAuthenticationResultForAugmentedAutofillLocked(data, authenticationId);
             // Augmented autofill is not logged.
-            mPresentationStatsEventLogger.logAndEndEvent();
+            mPresentationStatsEventLogger.logAndEndEvent("authentication - augmented");
             return;
         }
         if (mResponses == null) {
@@ -2859,7 +2862,7 @@
             Slog.w(TAG, "setAuthenticationResultLocked(" + authenticationId + "): no responses");
             mPresentationStatsEventLogger.maybeSetAuthenticationResult(
                     AUTHENTICATION_RESULT_FAILURE);
-            mPresentationStatsEventLogger.logAndEndEvent();
+            mPresentationStatsEventLogger.logAndEndEvent("authentication - no response");
             removeFromService();
             return;
         }
@@ -2870,7 +2873,7 @@
             Slog.w(TAG, "no authenticated response");
             mPresentationStatsEventLogger.maybeSetAuthenticationResult(
                     AUTHENTICATION_RESULT_FAILURE);
-            mPresentationStatsEventLogger.logAndEndEvent();
+            mPresentationStatsEventLogger.logAndEndEvent("authentication - bad response");
             removeFromService();
             return;
         }
@@ -2885,7 +2888,7 @@
                 Slog.w(TAG, "no dataset with index " + datasetIdx + " on fill response");
                 mPresentationStatsEventLogger.maybeSetAuthenticationResult(
                         AUTHENTICATION_RESULT_FAILURE);
-                mPresentationStatsEventLogger.logAndEndEvent();
+                mPresentationStatsEventLogger.logAndEndEvent("authentication - no datasets");
                 removeFromService();
                 return;
             }
@@ -3330,7 +3333,7 @@
 
         mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
                 PresentationStatsEventLogger.getNoPresentationEventReason(commitReason));
-        mPresentationStatsEventLogger.logAndEndEvent();
+        mPresentationStatsEventLogger.logAndEndEvent("Context committed");
 
         final int flags = lastResponse.getFlags();
         if ((flags & FillResponse.FLAG_TRACK_CONTEXT_COMMITED) == 0) {
@@ -4299,6 +4302,7 @@
      * Starts (if necessary) a new fill request upon entering a view.
      *
      * <p>A new request will be started in 2 scenarios:
+     *
      * <ol>
      *   <li>If the user manually requested autofill.
      *   <li>If the view is part of a new partition.
@@ -4307,18 +4311,17 @@
      * @param id The id of the view that is entered.
      * @param viewState The view that is entered.
      * @param flags The flag that was passed by the AutofillManager.
-     *
      * @return {@code true} if a new fill response is requested.
      */
     @GuardedBy("mLock")
-    private boolean requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
-            @NonNull ViewState viewState, int flags) {
+    private Optional<Integer> requestNewFillResponseOnViewEnteredIfNecessaryLocked(
+            @NonNull AutofillId id, @NonNull ViewState viewState, int flags) {
         // Force new response for manual request
         if ((flags & FLAG_MANUAL_REQUEST) != 0) {
             mSessionFlags.mAugmentedAutofillOnly = false;
             if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
-            requestNewFillResponseLocked(viewState, ViewState.STATE_RESTARTED_SESSION, flags);
-            return true;
+            return requestNewFillResponseLocked(
+                    viewState, ViewState.STATE_RESTARTED_SESSION, flags);
         }
 
         // If it's not, then check if it should start a partition.
@@ -4331,15 +4334,15 @@
             // Sometimes activity contain IMPORTANT_FOR_AUTOFILL_NO fields which marks session as
             // augmentedOnly, but other fields are still fillable by standard autofill.
             mSessionFlags.mAugmentedAutofillOnly = false;
-            requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_PARTITION, flags);
-            return true;
+            return requestNewFillResponseLocked(
+                    viewState, ViewState.STATE_STARTED_PARTITION, flags);
         }
 
         if (sVerbose) {
             Slog.v(TAG, "Not starting new partition for view " + id + ": "
                     + viewState.getStateAsString());
         }
-        return false;
+        return Optional.empty();
     }
 
     /**
@@ -4428,31 +4431,32 @@
     void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action,
             int flags) {
         if (mDestroyed) {
-            Slog.w(TAG, "Call to Session#updateLocked() rejected - session: "
-                    + id + " destroyed");
+            Slog.w(TAG, "updateLocked(" + id + "):  rejected - session: destroyed");
             return;
         }
         if (action == ACTION_RESPONSE_EXPIRED) {
             mSessionFlags.mExpiredResponse = true;
             if (sDebug) {
-                Slog.d(TAG, "Set the response has expired.");
+                Slog.d(TAG, "updateLocked(" + id + "): Set the response has expired.");
             }
             mPresentationStatsEventLogger.maybeSetNoPresentationEventReasonIfNoReasonExists(
                         NOT_SHOWN_REASON_VIEW_CHANGED);
-            mPresentationStatsEventLogger.logAndEndEvent();
+            mPresentationStatsEventLogger.logAndEndEvent("ACTION_RESPONSE_EXPIRED");
             return;
         }
 
         id.setSessionId(this.id);
-        if (sVerbose) {
-            Slog.v(TAG, "updateLocked(" + this.id + "): id=" + id + ", action="
-                    + actionAsString(action) + ", flags=" + flags);
-        }
         ViewState viewState = mViewStates.get(id);
         if (sVerbose) {
-            Slog.v(TAG, "updateLocked(" + this.id + "): mCurrentViewId=" + mCurrentViewId
-                    + ", mExpiredResponse=" + mSessionFlags.mExpiredResponse
-                    + ", viewState=" + viewState);
+            Slog.v(
+                    TAG,
+                    "updateLocked(" + id + "): "
+                            + "id=" + this.id
+                            + ", action=" + actionAsString(action)
+                            + ", flags=" + flags
+                            + ", mCurrentViewId=" + mCurrentViewId
+                            + ", mExpiredResponse=" + mSessionFlags.mExpiredResponse
+                            + ", viewState=" + viewState);
         }
 
         if (viewState == null) {
@@ -4505,14 +4509,14 @@
                     mSessionFlags.mFillDialogDisabled = true;
                     mPreviouslyFillDialogPotentiallyStarted = false;
                 } else {
-                    // Set the default reason for now if the user doesn't trigger any focus event
-                    // on the autofillable view. This can be changed downstream when more
-                    // information is available or session is committed.
-                    mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
-                            NOT_SHOWN_REASON_NO_FOCUS);
                     mPreviouslyFillDialogPotentiallyStarted = true;
                 }
-                requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags);
+                Optional<Integer> maybeRequestId =
+                        requestNewFillResponseLocked(
+                                viewState, ViewState.STATE_STARTED_SESSION, flags);
+                if (maybeRequestId.isPresent()) {
+                    mPresentationStatsEventLogger.maybeSetRequestId(maybeRequestId.get());
+                }
                 break;
             case ACTION_VALUE_CHANGED:
                 if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
@@ -4577,8 +4581,10 @@
                 }
                 boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
                 if (shouldRequestSecondaryProvider(flags)) {
-                    if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(
-                            id, viewState, flags)) {
+                    Optional<Integer> maybeRequestIdCred =
+                            requestNewFillResponseOnViewEnteredIfNecessaryLocked(
+                                    id, viewState, flags);
+                    if (maybeRequestIdCred.isPresent()) {
                         Slog.v(TAG, "Started a new fill request for secondary provider.");
                         return;
                     }
@@ -4622,17 +4628,7 @@
                     mLogViewEntered = true;
                 }
 
-                // Previously, fill request will only start whenever a view is entered.
-                // With Fill Dialog, request starts prior to view getting entered. So, we can't end
-                // the event at this moment, otherwise we will be wrongly attributing fill dialog
-                // event as concluded.
-                if (!wasPreviouslyFillDialog && !isSameViewAgain) {
-                    // TODO(b/319872477): Re-consider this logic below
-                    mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
-                            NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED);
-                    mPresentationStatsEventLogger.logAndEndEvent();
-                }
-
+                // Trigger augmented autofill if applicable
                 if ((flags & FLAG_MANUAL_REQUEST) == 0) {
                     // Not a manual request
                     if (mAugmentedAutofillableIds != null && mAugmentedAutofillableIds.contains(
@@ -4658,26 +4654,25 @@
                         return;
                     }
                 }
-                // If previous request was FillDialog request, a logger event was already started
-                if (!wasPreviouslyFillDialog) {
+
+                Optional<Integer> maybeNewRequestId =
+                        requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags);
+
+                // Previously, fill request will only start whenever a view is entered.
+                // With Fill Dialog, request starts prior to view getting entered. So, we can't end
+                // the event at this moment, otherwise we will be wrongly attributing fill dialog
+                // event as concluded.
+                if (!wasPreviouslyFillDialog
+                        && (!isSameViewEntered || maybeNewRequestId.isPresent())) {
                     startNewEventForPresentationStatsEventLogger();
-                }
-                if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags)) {
-                    // If a new request was issued even if previously it was fill dialog request,
-                    // we should end the log event, and start a new one. However, it leaves us
-                    // susceptible to race condition. But since mPresentationStatsEventLogger is
-                    // lock guarded, we should be safe.
-                    if (wasPreviouslyFillDialog) {
-                        mPresentationStatsEventLogger.logAndEndEvent();
-                        startNewEventForPresentationStatsEventLogger();
+                    if (maybeNewRequestId.isPresent()) {
+                        mPresentationStatsEventLogger.maybeSetRequestId(maybeNewRequestId.get());
                     }
-                    return;
                 }
 
-                FillResponse response = viewState.getResponse();
-                if (response != null) {
-                    logPresentationStatsOnViewEnteredLocked(response, isCredmanRequested);
-                }
+                logPresentationStatsOnViewEnteredLocked(
+                        viewState.getResponse(), isCredmanRequested);
+                mPresentationStatsEventLogger.updateTextFieldLength(value);
 
                 if (isSameViewEntered) {
                     setFillDialogDisabledAndStartInput();
@@ -4719,13 +4714,17 @@
     @GuardedBy("mLock")
     private void logPresentationStatsOnViewEnteredLocked(FillResponse response,
             boolean isCredmanRequested) {
-        mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId());
         mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
         mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
                 mFieldClassificationIdSnapshot);
-        mPresentationStatsEventLogger.maybeSetAvailableCount(
-                response.getDatasets(), mCurrentViewId);
+        mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
         mPresentationStatsEventLogger.maybeSetFocusedId(mCurrentViewId);
+
+        if (response != null) {
+            mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId());
+            mPresentationStatsEventLogger.maybeSetAvailableCount(
+                    response.getDatasets(), mCurrentViewId);
+        }
     }
 
     @GuardedBy("mLock")
@@ -4796,8 +4795,12 @@
 
         viewState.setCurrentValue(value);
         final String filterText = textValue;
-
         final AutofillValue filledValue = viewState.getAutofilledValue();
+
+        if (textValue != null) {
+            mPresentationStatsEventLogger.onFieldTextUpdated(viewState, value);
+        }
+
         if (filledValue != null) {
             if (filledValue.equals(value)) {
                 // When the update is caused by autofilling the view, just update the
@@ -4821,9 +4824,6 @@
                 currentView.maybeCallOnFillReady(flags);
             }
         }
-        if (textValue != null) {
-            mPresentationStatsEventLogger.onFieldTextUpdated(viewState, textValue.length());
-        }
 
         if (viewState.id.equals(this.mCurrentViewId)
                 && (viewState.getState() & ViewState.STATE_INLINE_SHOWN) != 0) {
@@ -4888,7 +4888,7 @@
                 mSaveEventLogger.logAndEndEvent();
                 mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
                     NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY);
-                mPresentationStatsEventLogger.logAndEndEvent();
+                mPresentationStatsEventLogger.logAndEndEvent("on fill ready");
                 return;
             }
         }
@@ -4920,7 +4920,6 @@
                 synchronized (mLock) {
                     final ViewState currentView = mViewStates.get(mCurrentViewId);
                     currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN);
-                    mPresentationStatsEventLogger.maybeSetDisplayPresentationType(UI_TYPE_DIALOG);
                 }
                 // Just show fill dialog once, so disabled after shown.
                 // Note: Cannot disable before requestShowFillDialog() because the method
@@ -6086,6 +6085,11 @@
     private void startNewEventForPresentationStatsEventLogger() {
         synchronized (mLock) {
             mPresentationStatsEventLogger.startNewEvent();
+            // Set the default reason for now if the user doesn't trigger any focus event
+            // on the autofillable view. This can be changed downstream when more
+            // information is available or session is committed.
+            mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
+                    NOT_SHOWN_REASON_NO_FOCUS);
             mPresentationStatsEventLogger.maybeSetDetectionPreference(
                     getDetectionPreferenceForLogging());
             mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
@@ -6724,7 +6728,7 @@
             SystemClock.elapsedRealtime() - mStartTime);
         mFillRequestEventLogger.logAndEndEvent();
         mFillResponseEventLogger.logAndEndEvent();
-        mPresentationStatsEventLogger.logAndEndEvent();
+        mPresentationStatsEventLogger.logAndEndEvent("log all events");
         mSaveEventLogger.logAndEndEvent();
         mSessionCommittedEventLogger.logAndEndEvent();
     }
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index a10039f..2446a6d 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -461,9 +461,9 @@
                         }
 
                         @Override
-                        public void onShown() {
+                        public void onShown(int datasetsShown) {
                             if (mCallback != null) {
-                                mCallback.onShown(UI_TYPE_DIALOG, response.getDatasets().size());
+                                mCallback.onShown(UI_TYPE_DIALOG, datasetsShown);
                             }
                         }
 
diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
index 5a71b89..c7b6be6 100644
--- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
@@ -85,7 +85,7 @@
         void onDatasetPicked(@NonNull Dataset dataset);
         void onDismissed();
         void onCanceled();
-        void onShown();
+        void onShown(int datasetsShown);
         void startIntentSender(IntentSender intentSender);
     }
 
@@ -155,7 +155,8 @@
         mDialog.setContentView(decor);
         setDialogParamsAsBottomSheet();
         mDialog.setOnCancelListener((d) -> mCallback.onCanceled());
-        mDialog.setOnShowListener((d) -> mCallback.onShown());
+        int datasetsShown = (mAdapter != null) ? mAdapter.getCount() : 0;
+        mDialog.setOnShowListener((d) -> mCallback.onShown(datasetsShown));
         show();
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3ac91b3..d169b1e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3292,6 +3292,12 @@
             return false;
         }
 
+        // Check if this activity is the top activity of its task - this prevents any trampolines
+        // followed by enterPictureInPictureMode() calls by an activity from below in its stack.
+        if (getTask().getTopMostActivity() != this) {
+            return false;
+        }
+
         // Check to see if PiP is supported for the display this container is on.
         if (mDisplayContent != null && !mDisplayContent.mDwpcHelper.isEnteringPipAllowed(
                 getUid())) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 02c8a49..20c5f02 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -1506,10 +1506,13 @@
         PackageManager pm = mService.mContext.getPackageManager();
         ApplicationInfo applicationInfo;
 
+        final int sourceUserId = UserHandle.getUserId(sourceUid);
         try {
-            applicationInfo = pm.getApplicationInfo(packageName, 0);
+            applicationInfo = pm.getApplicationInfoAsUser(packageName, /* flags= */ 0,
+                    sourceUserId);
         } catch (PackageManager.NameNotFoundException e) {
-            Slog.wtf(TAG, "Package name: " + packageName + " not found.");
+            Slog.wtf(TAG, "Package name: " + packageName + " not found for user "
+                    + sourceUserId);
             return bas.optedIn(ar);
         }
 
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 3ebaf03..9f1966b 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -255,8 +255,18 @@
             inOutConfig.windowConfiguration.setAppBounds(
                     newParentConfiguration.windowConfiguration.getBounds());
             outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
-            outAppBounds.inset(displayContent.getDisplayPolicy()
-                    .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets);
+            if (inOutConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+                final DisplayPolicy.DecorInsets.Info decor =
+                        displayContent.getDisplayPolicy().getDecorInsetsInfo(rotation, dw, dh);
+                if (outAppBounds.contains(decor.mOverrideNonDecorFrame)) {
+                    outAppBounds.intersect(decor.mOverrideNonDecorFrame);
+                }
+            } else {
+                // TODO(b/358509380): Handle other windowing mode like split screen and freeform
+                //  cases correctly.
+                outAppBounds.inset(displayContent.getDisplayPolicy()
+                        .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets);
+            }
         }
         float density = inOutConfig.densityDpi;
         if (density == Configuration.DENSITY_DPI_UNDEFINED) {
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 2f0ee17..f40f2617 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_DESTROY;
@@ -183,7 +184,7 @@
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
         if (settings.mRemoveContentMode == REMOVE_CONTENT_MODE_UNDEFINED) {
-            if (dc.isPrivate()) {
+            if (dc.isPrivate() || dc.getDisplay().getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT) {
                 // For private displays by default content is destroyed on removal.
                 return REMOVE_CONTENT_MODE_DESTROY;
             }
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 329d11b..3a40943 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1780,11 +1780,6 @@
         if (resuming != null) {
             // We do not want to trigger auto-PiP upon launch of a translucent activity.
             final boolean resumingOccludesParent = resuming.occludesParent();
-            // Resuming the new resume activity only if the previous activity can't go into Pip
-            // since we want to give Pip activities a chance to enter Pip before resuming the
-            // next activity.
-            final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState(
-                    "shouldAutoPipWhilePausing", userLeaving);
 
             if (ActivityTaskManagerService.isPip2ExperimentEnabled()) {
                 // If a new task is being launched, then mark the existing top activity as
@@ -1794,6 +1789,12 @@
                 Task.enableEnterPipOnTaskSwitch(prev, resuming.getTask(),
                         resuming, resuming.getOptions());
             }
+
+            // Resuming the new resume activity only if the previous activity can't go into Pip
+            // since we want to give Pip activities a chance to enter Pip before resuming the
+            // next activity.
+            final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState(
+                    "shouldAutoPipWhilePausing", userLeaving);
             if (prev.supportsEnterPipOnTaskSwitch && userLeaving
                     && resumingOccludesParent && lastResumedCanPip
                     && prev.pictureInPictureArgs.isAutoEnterEnabled()) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
index 3931580..d80a1f0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
@@ -18,13 +18,20 @@
 
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_HOVER_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
 
+import static junit.framework.Assert.assertFalse;
+
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.testng.AssertJUnit.assertTrue;
 
 import android.annotation.NonNull;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -32,8 +39,10 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.accessibility.AccessibilityTraceManager;
+import com.android.server.accessibility.Flags;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -45,6 +54,9 @@
 @RunWith(AndroidJUnit4.class)
 public class MagnificationGestureHandlerTest {
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private TestMagnificationGestureHandler mMgh;
     private static final int DISPLAY_0 = 0;
     private static final int FULLSCREEN_MODE =
@@ -81,6 +93,66 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+    public void onMotionEvent_isFromMouse_handleMouseOrStylusEvent() {
+        final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
+        mouseEvent.setSource(InputDevice.SOURCE_MOUSE);
+
+        mMgh.onMotionEvent(mouseEvent, mouseEvent, /* policyFlags= */ 0);
+
+        try {
+            assertTrue(mMgh.mIsHandleMouseOrStylusEventCalled);
+        } finally {
+            mouseEvent.recycle();
+        }
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+    public void onMotionEvent_isFromStylus_handleMouseOrStylusEvent() {
+        final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
+        stylusEvent.setSource(InputDevice.SOURCE_STYLUS);
+
+        mMgh.onMotionEvent(stylusEvent, stylusEvent, /* policyFlags= */ 0);
+
+        try {
+            assertTrue(mMgh.mIsHandleMouseOrStylusEventCalled);
+        } finally {
+            stylusEvent.recycle();
+        }
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+    public void onMotionEvent_isFromMouse_handleMouseOrStylusEventNotCalled() {
+        final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
+        mouseEvent.setSource(InputDevice.SOURCE_MOUSE);
+
+        mMgh.onMotionEvent(mouseEvent, mouseEvent, /* policyFlags= */ 0);
+
+        try {
+            assertFalse(mMgh.mIsHandleMouseOrStylusEventCalled);
+        } finally {
+            mouseEvent.recycle();
+        }
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+    public void onMotionEvent_isFromStylus_handleMouseOrStylusEventNotCalled() {
+        final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
+        stylusEvent.setSource(InputDevice.SOURCE_STYLUS);
+
+        mMgh.onMotionEvent(stylusEvent, stylusEvent, /* policyFlags= */ 0);
+
+        try {
+            assertFalse(mMgh.mIsHandleMouseOrStylusEventCalled);
+        } finally {
+            stylusEvent.recycle();
+        }
+    }
+
+    @Test
     public void onMotionEvent_downEvent_handleInteractionStart() {
         final MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
         downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
@@ -125,6 +197,7 @@
     private static class TestMagnificationGestureHandler extends MagnificationGestureHandler {
 
         boolean mIsInternalMethodCalled = false;
+        boolean mIsHandleMouseOrStylusEventCalled = false;
 
         TestMagnificationGestureHandler(int displayId, boolean detectSingleFingerTripleTap,
                 boolean detectTwoFingerTripleTap,
@@ -135,6 +208,11 @@
         }
 
         @Override
+        void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+            mIsHandleMouseOrStylusEventCalled = true;
+        }
+
+        @Override
         void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
             mIsInternalMethodCalled = true;
         }
diff --git a/services/tests/servicestests/src/com/android/server/autofill/PresentationEventLoggerTest.java b/services/tests/servicestests/src/com/android/server/autofill/PresentationEventLoggerTest.java
new file mode 100644
index 0000000..75258f0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/autofill/PresentationEventLoggerTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.autofill;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class PresentationEventLoggerTest {
+
+    @Test
+    public void testViewEntered() {
+        PresentationStatsEventLogger pEventLogger =
+                PresentationStatsEventLogger.createPresentationLog(1, 1, 1);
+
+        AutofillId id = new AutofillId(13);
+        AutofillValue initialValue = AutofillValue.forText("hello");
+        AutofillValue lastValue = AutofillValue.forText("hello world");
+        ViewState vState = new ViewState(id, null, 0, false);
+
+        pEventLogger.startNewEvent();
+        pEventLogger.maybeSetFocusedId(id);
+        pEventLogger.onFieldTextUpdated(vState, initialValue);
+        pEventLogger.onFieldTextUpdated(vState, lastValue);
+
+        PresentationStatsEventLogger.PresentationStatsEventInternal event =
+                pEventLogger.getInternalEvent().get();
+        assertThat(event).isNotNull();
+        assertThat(event.mFieldFirstLength).isEqualTo(initialValue.getTextValue().length());
+        assertThat(event.mFieldLastLength).isEqualTo(lastValue.getTextValue().length());
+        assertThat(event.mFieldModifiedFirstTimestampMs).isNotEqualTo(-1);
+        assertThat(event.mFieldModifiedLastTimestampMs).isNotEqualTo(-1);
+    }
+
+    @Test
+    public void testViewAutofilled() {
+        PresentationStatsEventLogger pEventLogger =
+                PresentationStatsEventLogger.createPresentationLog(1, 1, 1);
+
+        String newTextValue = "hello";
+        AutofillValue value = AutofillValue.forText(newTextValue);
+        AutofillId id = new AutofillId(13);
+        ViewState vState = new ViewState(id, null, ViewState.STATE_AUTOFILLED, false);
+
+        pEventLogger.startNewEvent();
+        pEventLogger.maybeSetFocusedId(id);
+        pEventLogger.onFieldTextUpdated(vState, value);
+
+        PresentationStatsEventLogger.PresentationStatsEventInternal event =
+                pEventLogger.getInternalEvent().get();
+        assertThat(event).isNotNull();
+        assertThat(event.mFieldFirstLength).isEqualTo(newTextValue.length());
+        assertThat(event.mFieldLastLength).isEqualTo(newTextValue.length());
+        assertThat(event.mAutofilledTimestampMs).isNotEqualTo(-1);
+        assertThat(event.mFieldModifiedFirstTimestampMs).isEqualTo(-1);
+        assertThat(event.mFieldModifiedLastTimestampMs).isEqualTo(-1);
+    }
+
+    @Test
+    public void testModifiedOnDifferentView() {
+        PresentationStatsEventLogger pEventLogger =
+                PresentationStatsEventLogger.createPresentationLog(1, 1, 1);
+
+        String newTextValue = "hello";
+        AutofillValue value = AutofillValue.forText(newTextValue);
+        AutofillId id = new AutofillId(13);
+        ViewState vState = new ViewState(id, null, ViewState.STATE_AUTOFILLED, false);
+
+        pEventLogger.startNewEvent();
+        pEventLogger.onFieldTextUpdated(vState, value);
+
+        PresentationStatsEventLogger.PresentationStatsEventInternal event =
+                pEventLogger.getInternalEvent().get();
+        assertThat(event).isNotNull();
+        assertThat(event.mFieldFirstLength).isEqualTo(-1);
+        assertThat(event.mFieldLastLength).isEqualTo(-1);
+        assertThat(event.mFieldModifiedFirstTimestampMs).isEqualTo(-1);
+        assertThat(event.mFieldModifiedLastTimestampMs).isEqualTo(-1);
+        assertThat(event.mAutofilledTimestampMs).isEqualTo(-1);
+    }
+
+    @Test
+    public void testSetCountShown() {
+        PresentationStatsEventLogger pEventLogger =
+                PresentationStatsEventLogger.createPresentationLog(1, 1, 1);
+
+        pEventLogger.startNewEvent();
+        pEventLogger.logWhenDatasetShown(7);
+
+        PresentationStatsEventLogger.PresentationStatsEventInternal event =
+                pEventLogger.getInternalEvent().get();
+        assertThat(event).isNotNull();
+        assertThat(event.mCountShown).isEqualTo(7);
+        assertThat(event.mNoPresentationReason)
+                .isEqualTo(PresentationStatsEventLogger.NOT_SHOWN_REASON_ANY_SHOWN);
+    }
+
+    @Test
+    public void testFillDialogShownThenInline() {
+        PresentationStatsEventLogger pEventLogger =
+                PresentationStatsEventLogger.createPresentationLog(1, 1, 1);
+
+        pEventLogger.startNewEvent();
+        pEventLogger.maybeSetDisplayPresentationType(3);
+        pEventLogger.maybeSetDisplayPresentationType(2);
+
+        PresentationStatsEventLogger.PresentationStatsEventInternal event =
+                pEventLogger.getInternalEvent().get();
+        assertThat(event).isNotNull();
+        assertThat(event.mDisplayPresentationType).isEqualTo(3);
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 36bfbef..c510eee 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -334,13 +334,14 @@
             entry: ZipEntry,
             out: ZipOutputStream,
             ) {
-        BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
+        // TODO: It seems like copying entries this way is _very_ slow,
+        // even with out.setLevel(0). Look for other ways to do it.
+
+        inZip.getInputStream(entry).use { ins ->
             // Copy unknown entries as is to the impl out. (but not to the stub out.)
             val outEntry = ZipEntry(entry.name)
             out.putNextEntry(outEntry)
-            while (bis.available() > 0) {
-                out.write(bis.read())
-            }
+            ins.transferTo(out)
             out.closeEntry()
         }
     }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
index ee4a06f..fcdf824 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
@@ -121,7 +121,7 @@
         return level.ordinal <= maxLogLevel.ordinal
     }
 
-    private fun println(level: LogLevel, message: String) {
+    fun println(level: LogLevel, message: String) {
         printers.forEach {
             if (it.logLevel.ordinal >= level.ordinal) {
                 it.println(level, indent, message)
@@ -129,7 +129,7 @@
         }
     }
 
-    private fun println(level: LogLevel, format: String, vararg args: Any?) {
+    fun println(level: LogLevel, format: String, vararg args: Any?) {
         if (isEnabled(level)) {
             println(level, String.format(format, *args))
         }
@@ -185,14 +185,29 @@
         println(LogLevel.Debug, format, *args)
     }
 
-    inline fun <T> iTime(message: String, block: () -> T): T {
+    inline fun <T> logTime(level: LogLevel, message: String, block: () -> T): T {
         val start = System.currentTimeMillis()
-        val ret = block()
-        val end = System.currentTimeMillis()
+        try {
+            return block()
+        } finally {
+            val end = System.currentTimeMillis()
+            if (isEnabled(level)) {
+                println(level,
+                    String.format("%s: took %.1f second(s).", message, (end - start) / 1000.0))
+            }
+        }
+    }
 
-        log.i("%s: took %.1f second(s).", message, (end - start) / 1000.0)
+    inline fun <T> iTime(message: String, block: () -> T): T {
+        return logTime(LogLevel.Info, message, block)
+    }
 
-        return ret
+    inline fun <T> vTime(message: String, block: () -> T): T {
+        return logTime(LogLevel.Verbose, message, block)
+    }
+
+    inline fun <T> dTime(message: String, block: () -> T): T {
+        return logTime(LogLevel.Debug, message, block)
     }
 
     inline fun forVerbose(block: () -> Unit) {
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index aa53005..2285880 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -26,7 +26,6 @@
 import com.github.javaparser.ast.Modifier
 import com.github.javaparser.ast.NodeList
 import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
-import com.github.javaparser.ast.body.InitializerDeclaration
 import com.github.javaparser.ast.expr.ArrayAccessExpr
 import com.github.javaparser.ast.expr.ArrayCreationExpr
 import com.github.javaparser.ast.expr.ArrayInitializerExpr
@@ -42,7 +41,10 @@
 import com.github.javaparser.ast.expr.ObjectCreationExpr
 import com.github.javaparser.ast.expr.SimpleName
 import com.github.javaparser.ast.expr.StringLiteralExpr
+import com.github.javaparser.ast.expr.VariableDeclarationExpr
 import com.github.javaparser.ast.stmt.BlockStmt
+import com.github.javaparser.ast.stmt.ReturnStmt
+import com.github.javaparser.ast.type.ClassOrInterfaceType
 import java.io.File
 import java.io.FileInputStream
 import java.io.FileNotFoundException
@@ -195,6 +197,7 @@
         groups: Map<String, LogGroup>,
         protoLogGroupsClassName: String
     ) {
+        var needsCreateLogGroupsMap = false
         classDeclaration.fields.forEach { field ->
             field.getAnnotationByClass(ProtoLogToolInjected::class.java)
                     .ifPresent { annotationExpr ->
@@ -222,33 +225,10 @@
                                             } ?: NullLiteralExpr())
                                 }
                                 ProtoLogToolInjected.Value.LOG_GROUPS.name -> {
-                                    val initializerBlockStmt = BlockStmt()
-                                    for (group in groups) {
-                                        initializerBlockStmt.addStatement(
-                                            MethodCallExpr()
-                                                    .setName("put")
-                                                    .setArguments(
-                                                        NodeList(StringLiteralExpr(group.key),
-                                                            FieldAccessExpr()
-                                                                    .setScope(
-                                                                        NameExpr(
-                                                                            protoLogGroupsClassName
-                                                                        ))
-                                                                    .setName(group.value.name)))
-                                        )
-                                        group.key
-                                    }
-
-                                    val treeMapCreation = ObjectCreationExpr()
-                                            .setType("TreeMap<String, IProtoLogGroup>")
-                                            .setAnonymousClassBody(NodeList(
-                                                InitializerDeclaration().setBody(
-                                                    initializerBlockStmt
-                                                )
-                                            ))
-
+                                    needsCreateLogGroupsMap = true
                                     field.setFinal(true)
-                                    field.variables.first().setInitializer(treeMapCreation)
+                                    field.variables.first().setInitializer(
+                                        MethodCallExpr().setName("createLogGroupsMap"))
                                 }
                                 ProtoLogToolInjected.Value.CACHE_UPDATER.name -> {
                                     field.setFinal(true)
@@ -261,6 +241,45 @@
                         }
                     }
         }
+
+        if (needsCreateLogGroupsMap) {
+            val body = BlockStmt()
+            body.addStatement(AssignExpr(
+                VariableDeclarationExpr(
+                    ClassOrInterfaceType("TreeMap<String, IProtoLogGroup>"),
+                    "result"
+                ),
+                ObjectCreationExpr().setType("TreeMap<String, IProtoLogGroup>"),
+                AssignExpr.Operator.ASSIGN
+            ))
+            for (group in groups) {
+                body.addStatement(
+                    MethodCallExpr(
+                        NameExpr("result"),
+                        "put",
+                        NodeList(
+                                StringLiteralExpr(group.key),
+                                FieldAccessExpr()
+                                        .setScope(
+                                            NameExpr(
+                                                protoLogGroupsClassName
+                                            ))
+                                        .setName(group.value.name)
+                        )
+                    )
+                )
+            }
+            body.addStatement(ReturnStmt(NameExpr("result")))
+
+            val method = classDeclaration.addMethod(
+                "createLogGroupsMap",
+                Modifier.Keyword.PRIVATE,
+                Modifier.Keyword.STATIC,
+                Modifier.Keyword.FINAL
+            )
+            method.setType("TreeMap<String, IProtoLogGroup>")
+            method.setBody(body)
+        }
     }
 
     private fun injectCacheClass(