Merge "Group session MediaItems together in OutputSwitcher" into main
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 5e0428b..05d6e88 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -29,8 +29,5 @@
 
 ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES}
 
-# This flag check hook runs only for "packages/SystemUI" subdirectory. If you want to include this check for other subdirectories, please modify flag_check.py.
-flag_hook = ${REPO_ROOT}/frameworks/base/packages/SystemUI/flag_check.py --msg=${PREUPLOAD_COMMIT_MESSAGE} --files=${PREUPLOAD_FILES} --project=${REPO_PROJECT}
-
 [Tool Paths]
 ktfmt = ${REPO_ROOT}/external/ktfmt/ktfmt.sh
diff --git a/apct-tests/perftests/core/Android.bp b/apct-tests/perftests/core/Android.bp
index 1e299cd..f16f2ca 100644
--- a/apct-tests/perftests/core/Android.bp
+++ b/apct-tests/perftests/core/Android.bp
@@ -50,6 +50,7 @@
         "junit-params",
         "core-tests-support",
         "guava",
+        "perfetto_trace_java_protos",
     ],
 
     libs: ["android.test.base.stubs.system"],
diff --git a/apct-tests/perftests/core/src/android/os/TracePerfTest.java b/apct-tests/perftests/core/src/android/os/TracePerfTest.java
index 0d64c39..bf7c96a 100644
--- a/apct-tests/perftests/core/src/android/os/TracePerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/TracePerfTest.java
@@ -17,6 +17,8 @@
 
 package android.os;
 
+import static android.os.PerfettoTrace.Category;
+
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
 import android.perftests.utils.ShellHelper;
@@ -31,19 +33,35 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import perfetto.protos.DataSourceConfigOuterClass.DataSourceConfig;
+import perfetto.protos.TraceConfigOuterClass.TraceConfig;
+import perfetto.protos.TraceConfigOuterClass.TraceConfig.BufferConfig;
+import perfetto.protos.TraceConfigOuterClass.TraceConfig.DataSource;
+import perfetto.protos.TrackEventConfigOuterClass.TrackEventConfig;
+
 @RunWith(AndroidJUnit4.class)
 public class TracePerfTest {
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
+    private static final String FOO = "foo";
+    private static final Category FOO_CATEGORY = new Category(FOO);
+    private static PerfettoTrace.Session sPerfettoSession;
+
     @BeforeClass
     public static void startTracing() {
         ShellHelper.runShellCommandRaw("atrace -c --async_start -a *");
+        PerfettoTrace.register(false /* isBackendInProcess */);
+        FOO_CATEGORY.register();
+        sPerfettoSession = new PerfettoTrace.Session(false /* isBackendInProcess */,
+                                                      getTraceConfig(FOO).toByteArray());
     }
 
     @AfterClass
     public static void endTracing() {
         ShellHelper.runShellCommandRaw("atrace --async_stop");
+        FOO_CATEGORY.unregister();
+        sPerfettoSession.close();
     }
 
     @Before
@@ -84,4 +102,61 @@
             Trace.setCounter("testCounter", 123);
         }
     }
+
+    @Test
+    public void testInstant() {
+        Trace.instant(Trace.TRACE_TAG_APP, "testInstantA");
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            Trace.instant(Trace.TRACE_TAG_APP, "testInstantA");
+        }
+    }
+
+    @Test
+    public void testInstantPerfetto() {
+        PerfettoTrace.instant(FOO_CATEGORY, "testInstantP").emit();
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            PerfettoTrace.instant(FOO_CATEGORY, "testInstantP").emit();
+        }
+    }
+
+    @Test
+    public void testInstantPerfettoWithArgs() {
+        PerfettoTrace.instant(FOO_CATEGORY, "testInstantP")
+                .addArg("foo", "bar")
+                .addFlow(1)
+                .emit();
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            PerfettoTrace.instant(FOO_CATEGORY, "testInstantP")
+                    .addArg("foo", "bar")
+                    .addFlow(1)
+                    .emit();
+        }
+    }
+
+    private static TraceConfig getTraceConfig(String cat) {
+        BufferConfig bufferConfig = BufferConfig.newBuilder().setSizeKb(1024).build();
+        TrackEventConfig trackEventConfig = TrackEventConfig
+                .newBuilder()
+                .addEnabledCategories(cat)
+                .build();
+        DataSourceConfig dsConfig = DataSourceConfig
+                .newBuilder()
+                .setName("track_event")
+                .setTargetBuffer(0)
+                .setTrackEventConfig(trackEventConfig)
+                .build();
+        DataSource ds = DataSource.newBuilder().setConfig(dsConfig).build();
+        TraceConfig traceConfig = TraceConfig
+                .newBuilder()
+                .addBuffers(bufferConfig)
+                .addDataSources(ds)
+                .build();
+        return traceConfig;
+    }
 }
diff --git a/core/api/current.txt b/core/api/current.txt
index 17e7d7a..a893fa5 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -20785,6 +20785,7 @@
     method public void registerDisplayListener(android.hardware.display.DisplayManager.DisplayListener, android.os.Handler);
     method @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public void registerDisplayListener(@NonNull java.util.concurrent.Executor, long, @NonNull android.hardware.display.DisplayManager.DisplayListener);
     method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener);
+    field @FlaggedApi("com.android.server.display.feature.flags.display_category_built_in") public static final String DISPLAY_CATEGORY_BUILT_IN_DISPLAYS = "android.hardware.display.category.BUILT_IN_DISPLAYS";
     field public static final String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION";
     field @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public static final long EVENT_TYPE_DISPLAY_ADDED = 1L; // 0x1L
     field @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public static final long EVENT_TYPE_DISPLAY_CHANGED = 4L; // 0x4L
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 7c1c868..85ab5ed 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1727,6 +1727,7 @@
     method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public void setShouldAlwaysRespectAppRequestedMode(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setUserDisabledHdrTypes(@NonNull int[]);
     method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public boolean shouldAlwaysRespectAppRequestedMode();
+    field public static final String DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED = "android.hardware.display.category.ALL_INCLUDING_DISABLED";
     field public static final String DISPLAY_CATEGORY_REAR = "android.hardware.display.category.REAR";
     field public static final String HDR_OUTPUT_CONTROL_FLAG = "enable_hdr_output_control";
     field public static final int SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS = 2; // 0x2
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 0590a06..b7460e9 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -146,6 +146,22 @@
             "android.hardware.display.category.PRESENTATION";
 
     /**
+     * Display category: Built in displays.
+     *
+     * <p>
+     *     This category can be used to identify displays that are built into the device. The
+     *     displays that are returned may be inactive or disabled at the current moment. The
+     *     returned displays are useful in identifying the various sizes of built-in displays. The
+     *     id from {@link Display#getDisplayId()} is not guaranteed to be stable and may change
+     *     when the display becomes active.
+     * </p>
+     * @see #getDisplays(String)
+     */
+    @FlaggedApi(com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_CATEGORY_BUILT_IN)
+    public static final String DISPLAY_CATEGORY_BUILT_IN_DISPLAYS =
+            "android.hardware.display.category.BUILT_IN_DISPLAYS";
+
+    /**
      * Display category: Rear displays.
      * <p>
      * This category can be used to identify complementary internal displays that are facing away
@@ -171,6 +187,8 @@
      * @see #getDisplays(String)
      * @hide
      */
+    @TestApi
+    @SuppressLint("UnflaggedApi")
     public static final String DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED =
             "android.hardware.display.category.ALL_INCLUDING_DISABLED";
 
@@ -623,6 +641,9 @@
      * is triggered whenever the properties of a {@link android.view.Display}, such as size,
      * state, density are modified.
      *
+     * This event is not triggered for refresh rate changes as they can change very often.
+     * To monitor refresh rate changes, subscribe to {@link EVENT_TYPE_DISPLAY_REFRESH_RATE}.
+     *
      * @see #registerDisplayListener(DisplayListener, Handler, long)
      *
      */
@@ -729,10 +750,13 @@
      * @see #DISPLAY_CATEGORY_PRESENTATION
      */
     public Display[] getDisplays(String category) {
-        boolean includeDisabled = (category != null
-                && category.equals(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED));
+        boolean includeDisabled = shouldIncludeDisabledDisplays(category);
         final int[] displayIds = mGlobal.getDisplayIds(includeDisabled);
-        if (DISPLAY_CATEGORY_PRESENTATION.equals(category)) {
+        if (Flags.displayCategoryBuiltIn()
+                && DISPLAY_CATEGORY_BUILT_IN_DISPLAYS.equals(category)) {
+            Display[] value = getDisplays(displayIds, DisplayManager::isBuiltInDisplay);
+            return value;
+        } else if (DISPLAY_CATEGORY_PRESENTATION.equals(category)) {
             return getDisplays(displayIds, DisplayManager::isPresentationDisplay);
         } else if (DISPLAY_CATEGORY_REAR.equals(category)) {
             return getDisplays(displayIds, DisplayManager::isRearDisplay);
@@ -742,6 +766,16 @@
         return new Display[0];
     }
 
+    private boolean shouldIncludeDisabledDisplays(@Nullable String category) {
+        if (DISPLAY_CATEGORY_BUILT_IN_DISPLAYS.equals(category)) {
+            return true;
+        }
+        if (DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) {
+            return true;
+        }
+        return false;
+    }
+
     private Display[] getDisplays(int[] displayIds, Predicate<Display> predicate) {
         ArrayList<Display> tmpDisplays = new ArrayList<>();
         for (int displayId : displayIds) {
@@ -753,6 +787,13 @@
         return tmpDisplays.toArray(new Display[tmpDisplays.size()]);
     }
 
+    private static boolean isBuiltInDisplay(@Nullable Display display) {
+        if (display == null) {
+            return false;
+        }
+        return display.getType() == Display.TYPE_INTERNAL;
+    }
+
     private static boolean isPresentationDisplay(@Nullable Display display) {
         if (display == null || (display.getDisplayId() == DEFAULT_DISPLAY)
                 || (display.getFlags() & Display.FLAG_PRESENTATION) == 0) {
@@ -801,6 +842,9 @@
      * Registers a display listener to receive notifications about when
      * displays are added, removed or changed.
      *
+     * We encourage to use {@link #registerDisplayListener(Executor, long, DisplayListener)}
+     * instead to subscribe for explicit events of interest
+     *
      * @param listener The listener to register.
      * @param handler The handler on which the listener should be invoked, or null
      * if the listener should be invoked on the calling thread's looper.
@@ -809,7 +853,9 @@
      */
     public void registerDisplayListener(DisplayListener listener, Handler handler) {
         registerDisplayListener(listener, handler, EVENT_TYPE_DISPLAY_ADDED
-                | EVENT_TYPE_DISPLAY_CHANGED | EVENT_TYPE_DISPLAY_REMOVED);
+                | EVENT_TYPE_DISPLAY_CHANGED
+                | EVENT_TYPE_DISPLAY_REFRESH_RATE
+                | EVENT_TYPE_DISPLAY_REMOVED);
     }
 
     /**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index b5715ed..339dbf2 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -1766,29 +1766,23 @@
         }
 
         if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_CHANGED) != 0) {
-            // For backward compatibility, a client subscribing to
-            // DisplayManager.EVENT_FLAG_DISPLAY_CHANGED will be enrolled to both Basic and
-            // RR changes
-            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
-                    | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED;
         }
 
-        if ((eventFlags
-                & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) {
+        if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) {
             baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
         }
 
-        if (Flags.displayListenerPerformanceImprovements()) {
-            if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) {
-                baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
-            }
+        if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) {
+            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+        }
 
+        if (Flags.displayListenerPerformanceImprovements()) {
             if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_STATE) != 0) {
                 baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_STATE;
             }
         }
 
-
         return baseEventMask;
     }
 }
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index cd48047..af40188 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -35,7 +35,6 @@
 import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
 import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiKeyGestures;
 import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS;
-import static com.android.input.flags.Flags.enableInputFilterRustImpl;
 import static com.android.input.flags.Flags.keyboardRepeatKeys;
 
 import android.Manifest;
@@ -883,7 +882,7 @@
      * @hide
      */
     public static boolean isAccessibilityBounceKeysFeatureEnabled() {
-        return keyboardA11yBounceKeysFlag() && enableInputFilterRustImpl();
+        return keyboardA11yBounceKeysFlag();
     }
 
     /**
@@ -967,7 +966,7 @@
      * @hide
      */
     public static boolean isAccessibilitySlowKeysFeatureFlagEnabled() {
-        return keyboardA11ySlowKeysFlag() && enableInputFilterRustImpl();
+        return keyboardA11ySlowKeysFlag();
     }
 
     /**
@@ -1053,7 +1052,7 @@
      * @hide
      */
     public static boolean isAccessibilityStickyKeysFeatureEnabled() {
-        return keyboardA11yStickyKeysFlag() && enableInputFilterRustImpl();
+        return keyboardA11yStickyKeysFlag();
     }
 
     /**
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 79323bf..ae017e8 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -57,16 +57,6 @@
 }
 
 flag {
-    namespace: "input_native"
-    name: "keyboard_layout_manager_multi_user_ime_setup"
-    description: "Update KeyboardLayoutManager to work correctly with multi-user IME setup"
-    bug: "354333072"
-    metadata {
-        purpose: PURPOSE_BUGFIX
-    }
-}
-
-flag {
     name: "modifier_shortcut_dump"
     namespace: "input"
     description: "Dump keyboard shortcuts in dumpsys window"
diff --git a/core/java/android/os/PerfettoTrace.java b/core/java/android/os/PerfettoTrace.java
index e3f251e..68f1570 100644
--- a/core/java/android/os/PerfettoTrace.java
+++ b/core/java/android/os/PerfettoTrace.java
@@ -154,14 +154,44 @@
         }
     }
 
+    /**
+     * Manages a perfetto tracing session.
+     * Constructing this object with a config automatically starts a tracing session. Each session
+     * must be closed after use and then the resulting trace bytes can be read.
+     *
+     * The session could be in process or system wide, depending on {@code isBackendInProcess}.
+     * This functionality is intended for testing.
+     */
+    public static final class Session {
+        private final long mPtr;
+
+        /**
+         * Session ctor.
+         */
+        public Session(boolean isBackendInProcess, byte[] config) {
+            mPtr = native_start_session(isBackendInProcess, config);
+        }
+
+        /**
+         * Closes the session and returns the trace.
+         */
+        public byte[] close() {
+            return native_stop_session(mPtr);
+        }
+    }
+
     @CriticalNative
     private static native long native_get_process_track_uuid();
-
     @CriticalNative
     private static native long native_get_thread_track_uuid(long tid);
 
     @FastNative
     private static native void native_activate_trigger(String name, int ttlMs);
+    @FastNative
+    private static native void native_register(boolean isBackendInProcess);
+
+    private static native long native_start_session(boolean isBackendInProcess, byte[] config);
+    private static native byte[] native_stop_session(long ptr);
 
     /**
      * Writes a trace message to indicate a given section of code was invoked.
@@ -307,7 +337,7 @@
     /**
      * Registers the process with Perfetto.
      */
-    public static void register() {
-        Trace.registerWithPerfetto();
+    public static void register(boolean isBackendInProcess) {
+        native_register(isBackendInProcess);
     }
 }
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 2a5666c..e769abe 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -624,6 +624,7 @@
             WAKE_REASON_TAP,
             WAKE_REASON_LIFT,
             WAKE_REASON_BIOMETRIC,
+            WAKE_REASON_DOCK,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface WakeReason{}
@@ -765,6 +766,12 @@
     public static final int WAKE_REASON_BIOMETRIC = 17;
 
     /**
+     * Wake up reason code: Waking up due to a user docking the device.
+     * @hide
+     */
+    public static final int WAKE_REASON_DOCK = 18;
+
+    /**
      * Convert the wake reason to a string for debugging purposes.
      * @hide
      */
@@ -788,6 +795,7 @@
             case WAKE_REASON_TAP: return "WAKE_REASON_TAP";
             case WAKE_REASON_LIFT: return "WAKE_REASON_LIFT";
             case WAKE_REASON_BIOMETRIC: return "WAKE_REASON_BIOMETRIC";
+            case WAKE_REASON_DOCK: return "WAKE_REASON_DOCK";
             default: return Integer.toString(wakeReason);
         }
     }
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 4a37e0a..09e6a45 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -164,8 +164,6 @@
     private static native void nativeInstant(long tag, String name);
     @FastNative
     private static native void nativeInstantForTrack(long tag, String trackName, String name);
-    @FastNative
-    private static native void nativeRegisterWithPerfetto();
 
     private Trace() {
     }
@@ -545,6 +543,6 @@
      * @hide
      */
     public static void registerWithPerfetto() {
-        nativeRegisterWithPerfetto();
+        PerfettoTrace.register(false /* isBackendInProcess */);
     }
 }
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index bd6ff4c..ae0e9c6 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -532,14 +532,30 @@
     public static final int FLAG_NO_FOCUS_CHANGE = MotionEventFlag.NO_FOCUS_CHANGE;
 
     /**
-     * This flag indicates that this event was modified by or generated from an accessibility
-     * service. Value = 0x800
+     * This flag indicates that this event was injected from some
+     * {@link android.accessibilityservice.AccessibilityService}, which may be either an
+     * Accessibility Tool OR a service using that API for purposes other than assisting users with
+     * disabilities. Value = 0x800
+     * @see #FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL
      * @hide
      */
     @TestApi
     public static final int FLAG_IS_ACCESSIBILITY_EVENT = MotionEventFlag.IS_ACCESSIBILITY_EVENT;
 
     /**
+     * This flag indicates that this event was injected from an
+     * {@link android.accessibilityservice.AccessibilityService} with the
+     * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool()} property
+     * set to true. These services (known as "Accessibility Tools") are used to assist users with
+     * disabilities, so events from these services should be able to reach all Views including
+     * Views which set {@link View#isAccessibilityDataSensitive()} to true.
+     * Value = 0x1000
+     * @hide
+     */
+    public static final int FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL =
+            MotionEventFlag.INJECTED_FROM_ACCESSIBILITY_TOOL;
+
+    /**
      * Private flag that indicates when the system has detected that this motion event
      * may be inconsistent with respect to the sequence of previously delivered motion events,
      * such as when a pointer move event is sent but the pointer is not down.
@@ -2534,6 +2550,24 @@
                 : flags & ~FLAG_TARGET_ACCESSIBILITY_FOCUS);
     }
 
+    /**
+     * @see #FLAG_IS_ACCESSIBILITY_EVENT
+     * @hide
+     */
+    public boolean isInjectedFromAccessibilityService() {
+        final int flags = getFlags();
+        return (flags & FLAG_IS_ACCESSIBILITY_EVENT) != 0;
+    }
+
+    /**
+     * @see #FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL
+     * @hide
+     */
+    public boolean isInjectedFromAccessibilityTool() {
+        final int flags = getFlags();
+        return (flags & FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL) != 0;
+    }
+
     /** @hide */
     public final boolean isHoverExitPending() {
         final int flags = getFlags();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7206906..0866e0d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16654,6 +16654,16 @@
             // Window is obscured, drop this touch.
             return false;
         }
+        if (android.view.accessibility.Flags.preventA11yNontoolFromInjectingIntoSensitiveViews()) {
+            if (event.isInjectedFromAccessibilityService()
+                    // If the event came from an Accessibility Service that does *not* declare
+                    // itself as AccessibilityServiceInfo#isAccessibilityTool and this View is
+                    // declared sensitive then drop the event.
+                    // Only Accessibility Tools are allowed to interact with sensitive Views.
+                    && !event.isInjectedFromAccessibilityTool() && isAccessibilityDataSensitive()) {
+                return false;
+            }
+        }
         return true;
     }
 
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index 6d2c0d00..bb8958b 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import static android.os.IInputConstants.POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY;
+import static android.os.IInputConstants.POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL;
 import static android.os.IInputConstants.POLICY_FLAG_KEY_GESTURE_TRIGGERED;
 
 import android.annotation.IntDef;
@@ -37,6 +38,7 @@
     int FLAG_VIRTUAL = 0x00000002;
 
     int FLAG_INJECTED_FROM_ACCESSIBILITY = POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY;
+    int FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL = POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL;
     int FLAG_KEY_GESTURE_TRIGGERED = POLICY_FLAG_KEY_GESTURE_TRIGGERED;
     int FLAG_INJECTED = 0x01000000;
     int FLAG_TRUSTED = 0x02000000;
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 294e5da..37f393e 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -4,6 +4,14 @@
 # NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
 
 flag {
+    name: "a11y_character_in_window_api"
+    namespace: "accessibility"
+    description: "Enables new extra data key for an AccessibilityService to request character bounds in unmagnified window coordinates."
+    bug: "375429616"
+    is_exported: true
+}
+
+flag {
     name: "a11y_expansion_state_api"
     namespace: "accessibility"
     description: "Enables new APIs for an app to convey if a node is expanded or collapsed."
@@ -42,23 +50,15 @@
 }
 
 flag {
-    name: "a11y_character_in_window_api"
-    namespace: "accessibility"
-    description: "Enables new extra data key for an AccessibilityService to request character bounds in unmagnified window coordinates."
-    bug: "375429616"
-    is_exported: true
-}
-
-flag {
-    namespace: "accessibility"
     name: "allow_shortcut_chooser_on_lockscreen"
+    namespace: "accessibility"
     description: "Allows the a11y shortcut disambig dialog to appear on the lockscreen"
     bug: "303871725"
 }
 
 flag {
-    namespace: "accessibility"
     name: "braille_display_hid"
+    namespace: "accessibility"
     is_exported: true
     description: "Enables new APIs for an AccessibilityService to communicate with a HID Braille display"
     bug: "303522222"
@@ -72,47 +72,62 @@
 }
 
 flag {
-    namespace: "accessibility"
     name: "collection_info_item_counts"
+    namespace: "accessibility"
     is_exported: true
     description: "Fields for total items and the number of important for accessibility items in a collection"
     bug: "302376158"
 }
 
 flag {
-    namespace: "accessibility"
     name: "copy_events_for_gesture_detection"
+    namespace: "accessibility"
     description: "Creates copies of MotionEvents and GestureEvents in GestureMatcher"
     bug: "280130713"
 }
 
 flag {
-    namespace: "accessibility"
     name: "deprecate_accessibility_announcement_apis"
+    namespace: "accessibility"
     description: "Controls the deprecation of platform APIs related to disruptive accessibility announcements"
     bug: "376727542"
     is_exported: true
 }
 
 flag {
-    namespace: "accessibility"
     name: "deprecate_ani_label_for_apis"
+    namespace: "accessibility"
     description: "Controls the deprecation of AccessibilityNodeInfo labelFor apis"
     bug: "333783827"
     is_exported: true
 }
 
 flag {
+    name: "enable_system_pinch_zoom_gesture"
     namespace: "accessibility"
+    description: "Feature flag for system pinch zoom gesture detector and related opt-out apis"
+    bug: "283323770"
+}
+
+flag {
+    name: "enable_type_window_control"
+    namespace: "accessibility"
+    is_exported: true
+    description: "adds new TYPE_WINDOW_CONTROL to AccessibilityWindowInfo for detecting Window Decorations"
+    bug: "320445550"
+}
+
+flag {
     name: "flash_notification_system_api"
+    namespace: "accessibility"
     is_exported: true
     description: "Makes flash notification APIs as system APIs for calling from mainline module"
     bug: "303131332"
 }
 
 flag {
-    namespace: "accessibility"
     name: "focus_rect_min_size"
+    namespace: "accessibility"
     description: "Ensures the a11y focus rect is big enough to be drawn as visible"
     bug: "368667566"
     metadata {
@@ -121,13 +136,45 @@
 }
 
 flag {
-    namespace: "accessibility"
     name: "force_invert_color"
+    namespace: "accessibility"
     description: "Enable force force-dark for smart inversion and dark theme everywhere"
     bug: "282821643"
 }
 
 flag {
+    name: "global_action_media_play_pause"
+    namespace: "accessibility"
+    description: "Allow AccessibilityService to perform GLOBAL_ACTION_MEDIA_PLAY_PAUSE"
+    bug: "334954140"
+    is_exported: true
+}
+
+flag {
+    name: "global_action_menu"
+    namespace: "accessibility"
+    description: "Allow AccessibilityService to perform GLOBAL_ACTION_MENU"
+    bug: "334954140"
+    is_exported: true
+}
+
+flag {
+    name: "granular_scrolling"
+    namespace: "accessibility"
+    is_exported: true
+    description: "Allow the use of granular scrolling. This allows scrollable nodes to scroll by increments other than a full screen"
+    bug: "302376158"
+}
+
+flag {
+    name: "indeterminate_range_info"
+    namespace: "accessibility"
+    description: "Creates a way to create an INDETERMINATE RangeInfo"
+    bug: "376108874"
+    is_exported: true
+}
+
+flag {
     name: "migrate_enable_shortcuts"
     namespace: "accessibility"
     description: "Refactors deprecated code to use AccessibilityManager#enableShortcutsForTargets."
@@ -146,70 +193,13 @@
 }
 
 flag {
+    name: "prevent_a11y_nontool_from_injecting_into_sensitive_views"
     namespace: "accessibility"
-    name: "global_action_menu"
-    description: "Allow AccessibilityService to perform GLOBAL_ACTION_MENU"
-    bug: "334954140"
-    is_exported: true
-}
-
-flag {
-    namespace: "accessibility"
-    name: "global_action_media_play_pause"
-    description: "Allow AccessibilityService to perform GLOBAL_ACTION_MEDIA_PLAY_PAUSE"
-    bug: "334954140"
-    is_exported: true
-}
-
-flag {
-    namespace: "accessibility"
-    name: "granular_scrolling"
-    is_exported: true
-    description: "Allow the use of granular scrolling. This allows scrollable nodes to scroll by increments other than a full screen"
-    bug: "302376158"
-}
-
-flag {
-    namespace: "accessibility"
-    name: "reduce_window_content_changed_event_throttle"
-    description: "Reduces the throttle of AccessibilityEvent of TYPE_WINDOW_CONTENT_CHANGED"
-    bug: "277305460"
-}
-
-flag {
-    namespace: "accessibility"
-    name: "remove_child_hover_check_for_touch_exploration"
-    description: "Remove a check for a hovered child that prevents touch events from being delegated to non-direct descendants"
-    bug: "304770837"
-}
-
-flag {
-    name: "skip_accessibility_warning_dialog_for_trusted_services"
-    namespace: "accessibility"
-    description: "Skips showing the accessibility warning dialog for trusted services."
-    bug: "303511250"
-}
-
-flag {
-    namespace: "accessibility"
-    name: "enable_type_window_control"
-    is_exported: true
-    description: "adds new TYPE_WINDOW_CONTROL to AccessibilityWindowInfo for detecting Window Decorations"
-    bug: "320445550"
-}
-
-flag {
-    namespace: "accessibility"
-    name: "update_always_on_a11y_service"
-    description: "Updates the Always-On A11yService state when the user changes the enablement of the shortcut."
-    bug: "298869916"
-}
-
-flag {
-    name: "enable_system_pinch_zoom_gesture"
-    namespace: "accessibility"
-    description: "Feature flag for system pinch zoom gesture detector and related opt-out apis"
-    bug: "283323770"
+    description: "Prevents injected gestures from A11yServices without isAccessibilityTool=true from reaching AccessibilityDataSensitive UI elements"
+    bug: "284180538"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
 }
 
 flag {
@@ -223,6 +213,30 @@
 }
 
 flag {
+    name: "reduce_window_content_changed_event_throttle"
+    namespace: "accessibility"
+    description: "Reduces the throttle of AccessibilityEvent of TYPE_WINDOW_CONTENT_CHANGED"
+    bug: "277305460"
+}
+
+flag {
+    name: "remove_child_hover_check_for_touch_exploration"
+    namespace: "accessibility"
+    description: "Remove a check for a hovered child that prevents touch events from being delegated to non-direct descendants"
+    bug: "304770837"
+}
+
+flag {
+    name: "restore_a11y_secure_settings_on_hsum_device"
+    namespace: "accessibility"
+    description: "Grab the a11y settings and send the settings restored broadcast for current visible foreground user"
+    bug: "381294327"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "restore_a11y_shortcut_target_service"
     namespace: "accessibility"
     description: "Perform merging and other bug fixes for SettingsProvider restore of ACCESSIBILITY_SHORTCUT_TARGET_SERVICES secure setting"
@@ -233,13 +247,10 @@
 }
 
 flag {
-    name: "restore_a11y_secure_settings_on_hsum_device"
+    name: "skip_accessibility_warning_dialog_for_trusted_services"
     namespace: "accessibility"
-    description: "Grab the a11y settings and send the settings restored broadcast for current visible foreground user"
-    bug: "381294327"
-    metadata {
-        purpose: PURPOSE_BUGFIX
-    }
+    description: "Skips showing the accessibility warning dialog for trusted services."
+    bug: "303511250"
 }
 
 flag {
@@ -274,6 +285,13 @@
 }
 
 flag {
+    namespace: "accessibility"
+    name: "update_always_on_a11y_service"
+    description: "Updates the Always-On A11yService state when the user changes the enablement of the shortcut."
+    bug: "298869916"
+}
+
+flag {
     name: "warning_use_default_dialog_type"
     namespace: "accessibility"
     description: "Uses the default type for the A11yService warning dialog, instead of SYSTEM_ALERT_DIALOG"
@@ -282,11 +300,3 @@
         purpose: PURPOSE_BUGFIX
     }
 }
-
-flag {
-    name: "indeterminate_range_info"
-    namespace: "accessibility"
-    description: "Creates a way to create an INDETERMINATE RangeInfo"
-    bug: "376108874"
-    is_exported: true
-}
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 3f2aa1c..2918610 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -148,28 +148,22 @@
         return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption);
     }
 
+    public static boolean isDesktopModeForcedEnabled() {
+        return getToggleOverride() == ToggleOverride.OVERRIDE_ON;
+    }
+
     private static boolean isFlagTrue(BooleanSupplier flagFunction,
             boolean shouldOverrideByDevOption) {
         if (!shouldOverrideByDevOption) return flagFunction.getAsBoolean();
         if (Flags.showDesktopExperienceDevOption()) {
-            return switch (getToggleOverride(null)) {
+            return switch (getToggleOverride()) {
                 case OVERRIDE_UNSET, OVERRIDE_OFF -> flagFunction.getAsBoolean();
                 case OVERRIDE_ON -> true;
             };
         }
         if (Flags.showDesktopWindowingDevOption()) {
-            Application application = ActivityThread.currentApplication();
-            if (application == null) {
-                Log.w(TAG, "Could not get the current application.");
-                return flagFunction.getAsBoolean();
-            }
-            ContentResolver contentResolver = application.getContentResolver();
-            if (contentResolver == null) {
-                Log.w(TAG, "Could not get the content resolver for the application.");
-                return flagFunction.getAsBoolean();
-            }
             boolean shouldToggleBeEnabledByDefault = Flags.enableDesktopWindowingMode();
-            return switch (getToggleOverride(contentResolver)) {
+            return switch (getToggleOverride()) {
                 case OVERRIDE_UNSET -> flagFunction.getAsBoolean();
                 // When toggle override matches its default state, don't override flags. This
                 // helps users reset their feature overrides.
@@ -180,14 +174,13 @@
         return flagFunction.getAsBoolean();
     }
 
-    private static ToggleOverride getToggleOverride(@Nullable ContentResolver contentResolver) {
+    private static ToggleOverride getToggleOverride() {
         // If cached, return it
         if (sCachedToggleOverride != null) {
             return sCachedToggleOverride;
         }
-
         // Otherwise, fetch and cache it
-        ToggleOverride override = getToggleOverrideFromSystem(contentResolver);
+        ToggleOverride override = getToggleOverrideFromSystem();
         sCachedToggleOverride = override;
         Log.d(TAG, "Toggle override initialized to: " + override);
         return override;
@@ -196,8 +189,7 @@
     /**
      *  Returns {@link ToggleOverride} from Settings.Global set by toggle.
      */
-    private static ToggleOverride getToggleOverrideFromSystem(
-            @Nullable ContentResolver contentResolver) {
+    private static ToggleOverride getToggleOverrideFromSystem() {
         int settingValue;
         if (Flags.showDesktopExperienceDevOption()) {
             settingValue = SystemProperties.getInt(
@@ -205,6 +197,16 @@
                     ToggleOverride.OVERRIDE_UNSET.getSetting()
             );
         } else {
+            final Application application = ActivityThread.currentApplication();
+            if (application == null) {
+                Log.w(TAG, "Could not get the current application.");
+                return ToggleOverride.OVERRIDE_UNSET;
+            }
+            final ContentResolver contentResolver = application.getContentResolver();
+            if (contentResolver == null) {
+                Log.w(TAG, "Could not get the content resolver for the application.");
+                return ToggleOverride.OVERRIDE_UNSET;
+            }
             settingValue = Settings.Global.getInt(
                     contentResolver,
                     Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 51d488f..d20b067 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -600,3 +600,13 @@
     description: "Enables split screen on non default displays"
     bug: "384999213"
 }
+
+flag {
+    name: "enable_desktop_mode_through_dev_option"
+    namespace: "lse_desktop_experience"
+    description: "Enables support for desktop mode through developer options for devices eligible for desktop mode."
+    bug: "382238347"
+    metadata {
+       purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/com/android/internal/os/BaseCommand.java b/core/java/com/android/internal/os/BaseCommand.java
index c85b5d7..af763e4 100644
--- a/core/java/com/android/internal/os/BaseCommand.java
+++ b/core/java/com/android/internal/os/BaseCommand.java
@@ -58,15 +58,23 @@
         mRawArgs = args;
         mArgs.init(null, null, null, null, args, 0);
 
+        int status = 1;
         try {
             onRun();
+            status = 0;
         } catch (IllegalArgumentException e) {
             onShowUsage(System.err);
             System.err.println();
             System.err.println("Error: " + e.getMessage());
+            status = 0;
         } catch (Exception e) {
             e.printStackTrace(System.err);
-            System.exit(1);
+        } finally {
+            System.out.flush();
+            System.err.flush();
+        }
+        if (status != 0) {
+            System.exit(status);
         }
     }
 
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 72cb9d1..98d1ef6 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -217,9 +217,9 @@
     void setUdfpsRefreshRateCallback(in IUdfpsRefreshRateRequestCallback callback);
 
     /**
-     * Notifies System UI that the display is ready to show system decorations.
+     * Notifies System UI that the system decorations should be added on the display.
      */
-    void onDisplayReady(int displayId);
+    void onDisplayAddSystemDecorations(int displayId);
 
     /**
      * Notifies System UI that the system decorations should be removed from the display.
diff --git a/core/jni/android_os_PerfettoTrace.cpp b/core/jni/android_os_PerfettoTrace.cpp
index 962aefc..9bedfa2 100644
--- a/core/jni/android_os_PerfettoTrace.cpp
+++ b/core/jni/android_os_PerfettoTrace.cpp
@@ -24,9 +24,12 @@
 #include <nativehelper/scoped_primitive_array.h>
 #include <nativehelper/scoped_utf_chars.h>
 #include <nativehelper/utils.h>
+#include <tracing_perfetto.h>
 #include <tracing_sdk.h>
 
 namespace android {
+constexpr int kFlushTimeoutMs = 5000;
+
 template <typename T>
 inline static T* toPointer(jlong ptr) {
     return reinterpret_cast<T*>(static_cast<uintptr_t>(ptr));
@@ -51,6 +54,10 @@
     tracing_perfetto::activate_trigger(name_chars.c_str(), static_cast<uint32_t>(ttl_ms));
 }
 
+void android_os_PerfettoTrace_register(bool is_backend_in_process) {
+    tracing_perfetto::registerWithPerfetto(is_backend_in_process);
+}
+
 static jlong android_os_PerfettoTraceCategory_init(JNIEnv* env, jclass, jstring name, jstring tag,
                                                    jstring severity) {
     ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
@@ -85,6 +92,36 @@
     return toJLong(category->get());
 }
 
+static jlong android_os_PerfettoTrace_start_session(JNIEnv* env, jclass /* obj */,
+                                                    jboolean is_backend_in_process,
+                                                    jbyteArray config_bytes) {
+    jsize length = env->GetArrayLength(config_bytes);
+    std::vector<uint8_t> data;
+    data.reserve(length);
+    env->GetByteArrayRegion(config_bytes, 0, length, reinterpret_cast<jbyte*>(data.data()));
+
+    tracing_perfetto::Session* session =
+            new tracing_perfetto::Session(is_backend_in_process, data.data(), length);
+
+    return reinterpret_cast<long>(session);
+}
+
+static jbyteArray android_os_PerfettoTrace_stop_session([[maybe_unused]] JNIEnv* env,
+                                                        jclass /* obj */, jlong ptr) {
+    tracing_perfetto::Session* session = reinterpret_cast<tracing_perfetto::Session*>(ptr);
+
+    session->FlushBlocking(kFlushTimeoutMs);
+    session->StopBlocking();
+
+    std::vector<uint8_t> data = session->ReadBlocking();
+
+    delete session;
+
+    jbyteArray bytes = env->NewByteArray(data.size());
+    env->SetByteArrayRegion(bytes, 0, data.size(), reinterpret_cast<jbyte*>(data.data()));
+    return bytes;
+}
+
 static const JNINativeMethod gCategoryMethods[] = {
         {"native_init", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J",
          (void*)android_os_PerfettoTraceCategory_init},
@@ -101,7 +138,10 @@
          {"native_get_thread_track_uuid", "(J)J",
           (void*)android_os_PerfettoTrace_get_thread_track_uuid},
          {"native_activate_trigger", "(Ljava/lang/String;I)V",
-          (void*)android_os_PerfettoTrace_activate_trigger}};
+          (void*)android_os_PerfettoTrace_activate_trigger},
+         {"native_register", "(Z)V", (void*)android_os_PerfettoTrace_register},
+         {"native_start_session", "(Z[B)J", (void*)android_os_PerfettoTrace_start_session},
+         {"native_stop_session", "(J)[B", (void*)android_os_PerfettoTrace_stop_session}};
 
 int register_android_os_PerfettoTrace(JNIEnv* env) {
     int res = jniRegisterNativeMethods(env, "android/os/PerfettoTrace", gTraceMethods,
diff --git a/core/jni/android_os_Trace.cpp b/core/jni/android_os_Trace.cpp
index 21e056d..50618c5 100644
--- a/core/jni/android_os_Trace.cpp
+++ b/core/jni/android_os_Trace.cpp
@@ -131,10 +131,6 @@
     return tracing_perfetto::isTagEnabled(tag);
 }
 
-static void android_os_Trace_nativeRegisterWithPerfetto(JNIEnv* env) {
-    tracing_perfetto::registerWithPerfetto();
-}
-
 static const JNINativeMethod gTraceMethods[] = {
         /* name, signature, funcPtr */
         {"nativeSetAppTracingAllowed", "(Z)V", (void*)android_os_Trace_nativeSetAppTracingAllowed},
@@ -157,7 +153,6 @@
         {"nativeInstant", "(JLjava/lang/String;)V", (void*)android_os_Trace_nativeInstant},
         {"nativeInstantForTrack", "(JLjava/lang/String;Ljava/lang/String;)V",
          (void*)android_os_Trace_nativeInstantForTrack},
-        {"nativeRegisterWithPerfetto", "()V", (void*)android_os_Trace_nativeRegisterWithPerfetto},
 
         // ----------- @CriticalNative  ----------------
         {"nativeIsTagEnabled", "(J)Z", (void*)android_os_Trace_nativeIsTagEnabled},
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 586cafd..a49e034 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7245,6 +7245,9 @@
     <!-- Whether desktop mode is supported on the current device  -->
     <bool name="config_isDesktopModeSupported">false</bool>
 
+    <!-- Whether the developer option for desktop mode is supported on the current device  -->
+    <bool name="config_isDesktopModeDevOptionSupported">false</bool>
+
     <!-- Maximum number of active tasks on a given Desktop Windowing session. Set to 0 for unlimited. -->
     <integer name="config_maxDesktopWindowingActiveTasks">0</integer>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 772a741..aca9d30 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5709,6 +5709,9 @@
   <!-- Whether desktop mode is supported on the current device  -->
   <java-symbol type="bool" name="config_isDesktopModeSupported" />
 
+  <!-- Whether the developer option for desktop mode is supported on the current device  -->
+  <java-symbol type="bool" name="config_isDesktopModeDevOptionSupported" />
+
   <!-- Maximum number of active tasks on a given Desktop Windowing session. Set to 0 for unlimited. -->
   <java-symbol type="integer" name="config_maxDesktopWindowingActiveTasks"/>
 
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 1b6746c..c06ad64 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -122,7 +122,6 @@
         "android.view.flags-aconfig-java",
     ],
     jni_libs: [
-        "libperfetto_trace_test_jni",
         "libpowermanagertest_jni",
         "libviewRootImplTest_jni",
         "libworksourceparceltest_jni",
diff --git a/core/tests/coretests/jni/Android.bp b/core/tests/coretests/jni/Android.bp
index 798ec90..d6379ca 100644
--- a/core/tests/coretests/jni/Android.bp
+++ b/core/tests/coretests/jni/Android.bp
@@ -111,27 +111,3 @@
     ],
     gtest: false,
 }
-
-cc_test_library {
-    name: "libperfetto_trace_test_jni",
-    srcs: [
-        "PerfettoTraceTest.cpp",
-    ],
-    static_libs: [
-        "perfetto_trace_protos",
-        "libtracing_perfetto_test_utils",
-    ],
-    shared_libs: [
-        "liblog",
-        "libnativehelper",
-        "libperfetto_c",
-        "libprotobuf-cpp-lite",
-        "libtracing_perfetto",
-    ],
-    stl: "libc++_static",
-    cflags: [
-        "-Werror",
-        "-Wall",
-    ],
-    gtest: false,
-}
diff --git a/core/tests/coretests/jni/PerfettoTraceTest.cpp b/core/tests/coretests/jni/PerfettoTraceTest.cpp
deleted file mode 100644
index 41d02ed7..0000000
--- a/core/tests/coretests/jni/PerfettoTraceTest.cpp
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// #define LOG_NDEBUG 0
-#define LOG_TAG "PerfettoTraceTest"
-
-#include <nativehelper/JNIHelp.h>
-#include <utils/Log.h>
-
-#include "jni.h"
-#include "perfetto/public/abi/data_source_abi.h"
-#include "perfetto/public/abi/heap_buffer.h"
-#include "perfetto/public/abi/pb_decoder_abi.h"
-#include "perfetto/public/abi/tracing_session_abi.h"
-#include "perfetto/public/abi/track_event_abi.h"
-#include "perfetto/public/compiler.h"
-#include "perfetto/public/data_source.h"
-#include "perfetto/public/pb_decoder.h"
-#include "perfetto/public/producer.h"
-#include "perfetto/public/protos/config/trace_config.pzc.h"
-#include "perfetto/public/protos/trace/interned_data/interned_data.pzc.h"
-#include "perfetto/public/protos/trace/test_event.pzc.h"
-#include "perfetto/public/protos/trace/trace.pzc.h"
-#include "perfetto/public/protos/trace/trace_packet.pzc.h"
-#include "perfetto/public/protos/trace/track_event/debug_annotation.pzc.h"
-#include "perfetto/public/protos/trace/track_event/track_descriptor.pzc.h"
-#include "perfetto/public/protos/trace/track_event/track_event.pzc.h"
-#include "perfetto/public/protos/trace/trigger.pzc.h"
-#include "perfetto/public/te_category_macros.h"
-#include "perfetto/public/te_macros.h"
-#include "perfetto/public/track_event.h"
-#include "protos/perfetto/trace/interned_data/interned_data.pb.h"
-#include "protos/perfetto/trace/trace.pb.h"
-#include "protos/perfetto/trace/trace_packet.pb.h"
-#include "tracing_perfetto.h"
-#include "utils.h"
-
-namespace android {
-using ::perfetto::protos::EventCategory;
-using ::perfetto::protos::EventName;
-using ::perfetto::protos::FtraceEvent;
-using ::perfetto::protos::FtraceEventBundle;
-using ::perfetto::protos::InternedData;
-using ::perfetto::protos::Trace;
-using ::perfetto::protos::TracePacket;
-
-using ::perfetto::shlib::test_utils::TracingSession;
-
-struct TracingSessionHolder {
-    TracingSession tracing_session;
-};
-
-static void nativeRegisterPerfetto([[maybe_unused]] JNIEnv* env, jclass /* obj */) {
-    tracing_perfetto::registerWithPerfetto(false /* test */);
-}
-
-static jlong nativeStartTracing(JNIEnv* env, jclass /* obj */, jbyteArray configBytes) {
-    jsize length = env->GetArrayLength(configBytes);
-    std::vector<uint8_t> data;
-    data.reserve(length);
-    env->GetByteArrayRegion(configBytes, 0, length, reinterpret_cast<jbyte*>(data.data()));
-
-    TracingSession session = TracingSession::FromBytes(data.data(), length);
-    TracingSessionHolder* holder = new TracingSessionHolder(std::move(session));
-
-    return reinterpret_cast<long>(holder);
-}
-
-static jbyteArray nativeStopTracing([[maybe_unused]] JNIEnv* env, jclass /* obj */, jlong ptr) {
-    TracingSessionHolder* holder = reinterpret_cast<TracingSessionHolder*>(ptr);
-
-    // Stop
-    holder->tracing_session.FlushBlocking(5000);
-    holder->tracing_session.StopBlocking();
-
-    std::vector<uint8_t> data = holder->tracing_session.ReadBlocking();
-
-    delete holder;
-
-    jbyteArray bytes = env->NewByteArray(data.size());
-    env->SetByteArrayRegion(bytes, 0, data.size(), reinterpret_cast<jbyte*>(data.data()));
-    return bytes;
-}
-
-extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
-    JNIEnv* env;
-    const JNINativeMethod methodTable[] = {/* name, signature, funcPtr */
-                                           {"nativeStartTracing", "([B)J",
-                                            (void*)nativeStartTracing},
-                                           {"nativeStopTracing", "(J)[B", (void*)nativeStopTracing},
-                                           {"nativeRegisterPerfetto", "()V",
-                                            (void*)nativeRegisterPerfetto}};
-
-    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
-        return JNI_ERR;
-    }
-
-    jniRegisterNativeMethods(env, "android/os/PerfettoTraceTest", methodTable,
-                             sizeof(methodTable) / sizeof(JNINativeMethod));
-
-    return JNI_VERSION_1_6;
-}
-
-} /* namespace android */
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 8fa5103..dc2f0a6 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -307,8 +307,10 @@
         assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
                 mDisplayManagerGlobal
                         .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_ADDED, 0));
-        assertEquals(DISPLAY_CHANGE_EVENTS, mDisplayManagerGlobal
-                .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED, 0));
+        assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED,
+                mDisplayManagerGlobal
+                        .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED,
+                                0));
         assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
                 mDisplayManagerGlobal.mapFiltersToInternalEventFlag(
                         DisplayManager.EVENT_TYPE_DISPLAY_REMOVED, 0));
diff --git a/core/tests/coretests/src/android/os/PerfettoTraceTest.java b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
index ad28383..0b5a446 100644
--- a/core/tests/coretests/src/android/os/PerfettoTraceTest.java
+++ b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
@@ -28,7 +28,6 @@
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.util.ArraySet;
-import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -84,19 +83,9 @@
     private final Set<String> mDebugAnnotationNames = new ArraySet<>();
     private final Set<String> mTrackNames = new ArraySet<>();
 
-    static {
-        try {
-            System.loadLibrary("perfetto_trace_test_jni");
-            Log.i(TAG, "Successfully loaded trace_test native library");
-        } catch (UnsatisfiedLinkError ule) {
-            Log.w(TAG, "Could not load trace_test native library");
-        }
-    }
-
     @Before
     public void setUp() {
-        PerfettoTrace.register();
-        nativeRegisterPerfetto();
+        PerfettoTrace.register(true);
         FOO_CATEGORY.register();
 
         mCategoryNames.clear();
@@ -110,7 +99,7 @@
     public void testDebugAnnotations() throws Exception {
         TraceConfig traceConfig = getTraceConfig(FOO);
 
-        long ptr = nativeStartTracing(traceConfig.toByteArray());
+        PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray());
 
         PerfettoTrace.instant(FOO_CATEGORY, "event")
                 .addFlow(2)
@@ -121,7 +110,7 @@
                 .addArg("string_val", FOO)
                 .emit();
 
-        byte[] traceBytes = nativeStopTracing(ptr);
+        byte[] traceBytes = session.close();
 
         Trace trace = Trace.parseFrom(traceBytes);
 
@@ -165,11 +154,11 @@
     public void testDebugAnnotationsWithLambda() throws Exception {
         TraceConfig traceConfig = getTraceConfig(FOO);
 
-        long ptr = nativeStartTracing(traceConfig.toByteArray());
+        PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray());
 
         PerfettoTrace.instant(FOO_CATEGORY, "event").addArg("long_val", 123L).emit();
 
-        byte[] traceBytes = nativeStopTracing(ptr);
+        byte[] traceBytes = session.close();
 
         Trace trace = Trace.parseFrom(traceBytes);
 
@@ -200,7 +189,7 @@
     public void testNamedTrack() throws Exception {
         TraceConfig traceConfig = getTraceConfig(FOO);
 
-        long ptr = nativeStartTracing(traceConfig.toByteArray());
+        PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray());
 
         PerfettoTrace.begin(FOO_CATEGORY, "event")
                 .usingNamedTrack(PerfettoTrace.getProcessTrackUuid(), FOO)
@@ -211,7 +200,7 @@
                 .usingNamedTrack(PerfettoTrace.getThreadTrackUuid(Process.myTid()), "bar")
                 .emit();
 
-        Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
+        Trace trace = Trace.parseFrom(session.close());
 
         boolean hasTrackEvent = false;
         boolean hasTrackUuid = false;
@@ -248,7 +237,7 @@
     public void testProcessThreadNamedTrack() throws Exception {
         TraceConfig traceConfig = getTraceConfig(FOO);
 
-        long ptr = nativeStartTracing(traceConfig.toByteArray());
+        PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray());
 
         PerfettoTrace.begin(FOO_CATEGORY, "event")
                 .usingProcessNamedTrack(FOO)
@@ -259,7 +248,7 @@
                 .usingThreadNamedTrack(Process.myTid(), "%s-%s", "bar", "stool")
                 .emit();
 
-        Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
+        Trace trace = Trace.parseFrom(session.close());
 
         boolean hasTrackEvent = false;
         boolean hasTrackUuid = false;
@@ -296,13 +285,13 @@
     public void testCounterSimple() throws Exception {
         TraceConfig traceConfig = getTraceConfig(FOO);
 
-        long ptr = nativeStartTracing(traceConfig.toByteArray());
+        PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray());
 
         PerfettoTrace.counter(FOO_CATEGORY, 16, FOO).emit();
 
         PerfettoTrace.counter(FOO_CATEGORY, 3.14, "bar").emit();
 
-        Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
+        Trace trace = Trace.parseFrom(session.close());
 
         boolean hasTrackEvent = false;
         boolean hasCounterValue = false;
@@ -339,7 +328,7 @@
     public void testCounter() throws Exception {
         TraceConfig traceConfig = getTraceConfig(FOO);
 
-        long ptr = nativeStartTracing(traceConfig.toByteArray());
+        PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray());
 
         PerfettoTrace.counter(FOO_CATEGORY, 16)
                 .usingCounterTrack(PerfettoTrace.getProcessTrackUuid(), FOO).emit();
@@ -348,7 +337,7 @@
                 .usingCounterTrack(PerfettoTrace.getThreadTrackUuid(Process.myTid()),
                                    "%s-%s", "bar", "stool").emit();
 
-        Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
+        Trace trace = Trace.parseFrom(session.close());
 
         boolean hasTrackEvent = false;
         boolean hasCounterValue = false;
@@ -385,14 +374,14 @@
     public void testProcessThreadCounter() throws Exception {
         TraceConfig traceConfig = getTraceConfig(FOO);
 
-        long ptr = nativeStartTracing(traceConfig.toByteArray());
+        PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray());
 
         PerfettoTrace.counter(FOO_CATEGORY, 16).usingProcessCounterTrack(FOO).emit();
 
         PerfettoTrace.counter(FOO_CATEGORY, 3.14)
                 .usingThreadCounterTrack(Process.myTid(), "%s-%s", "bar", "stool").emit();
 
-        Trace trace = Trace.parseFrom(nativeStopTracing(ptr));
+        Trace trace = Trace.parseFrom(session.close());
 
         boolean hasTrackEvent = false;
         boolean hasCounterValue = false;
@@ -429,7 +418,7 @@
     public void testProto() throws Exception {
         TraceConfig traceConfig = getTraceConfig(FOO);
 
-        long ptr = nativeStartTracing(traceConfig.toByteArray());
+        PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray());
 
         PerfettoTrace.instant(FOO_CATEGORY, "event_proto")
                 .beginProto()
@@ -441,7 +430,7 @@
                 .endProto()
                 .emit();
 
-        byte[] traceBytes = nativeStopTracing(ptr);
+        byte[] traceBytes = session.close();
 
         Trace trace = Trace.parseFrom(traceBytes);
 
@@ -477,7 +466,7 @@
     public void testProtoNested() throws Exception {
         TraceConfig traceConfig = getTraceConfig(FOO);
 
-        long ptr = nativeStartTracing(traceConfig.toByteArray());
+        PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray());
 
         PerfettoTrace.instant(FOO_CATEGORY, "event_proto_nested")
                 .beginProto()
@@ -494,7 +483,7 @@
                 .endProto()
                 .emit();
 
-        byte[] traceBytes = nativeStopTracing(ptr);
+        byte[] traceBytes = session.close();
 
         Trace trace = Trace.parseFrom(traceBytes);
 
@@ -538,13 +527,13 @@
     public void testActivateTrigger() throws Exception {
         TraceConfig traceConfig = getTriggerTraceConfig(FOO, FOO);
 
-        long ptr = nativeStartTracing(traceConfig.toByteArray());
+        PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray());
 
         PerfettoTrace.instant(FOO_CATEGORY, "event_trigger").emit();
 
         PerfettoTrace.activateTrigger(FOO, 1000);
 
-        byte[] traceBytes = nativeStopTracing(ptr);
+        byte[] traceBytes = session.close();
 
         Trace trace = Trace.parseFrom(traceBytes);
 
@@ -569,7 +558,7 @@
         TraceConfig traceConfig = getTraceConfig(BAR);
 
         Category barCategory = new Category(BAR);
-        long ptr = nativeStartTracing(traceConfig.toByteArray());
+        PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray());
 
         PerfettoTrace.instant(barCategory, "event")
                 .addArg("before", 1)
@@ -581,7 +570,7 @@
                 .addArg("after", 1)
                 .emit();
 
-        byte[] traceBytes = nativeStopTracing(ptr);
+        byte[] traceBytes = session.close();
 
         Trace trace = Trace.parseFrom(traceBytes);
 
@@ -603,10 +592,6 @@
         assertThat(mDebugAnnotationNames).doesNotContain("before");
     }
 
-    private static native long nativeStartTracing(byte[] config);
-    private static native void nativeRegisterPerfetto();
-    private static native byte[] nativeStopTracing(long ptr);
-
     private TrackEvent getTrackEvent(Trace trace, int idx) {
         int curIdx = 0;
         for (TracePacket packet: trace.getPacketList()) {
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 1ee71ca..e196880 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -212,10 +212,18 @@
     }
 
     /**
+     * Return {@code true} if the current device supports the developer option for desktop mode.
+     */
+    private static boolean isDesktopModeDevOptionSupported(@NonNull Context context) {
+        return context.getResources().getBoolean(R.bool.config_isDesktopModeDevOptionSupported);
+    }
+
+    /**
      * Return {@code true} if desktop mode dev option should be shown on current device
      */
     public static boolean canShowDesktopModeDevOption(@NonNull Context context) {
-        return isDeviceEligibleForDesktopMode(context) && Flags.showDesktopWindowingDevOption();
+        return isDeviceEligibleForDesktopModeDevOption(context)
+                && Flags.showDesktopWindowingDevOption();
     }
 
     /**
@@ -226,17 +234,25 @@
     }
 
     /** Returns if desktop mode dev option should be enabled if there is no user override. */
-    public static boolean shouldDevOptionBeEnabledByDefault() {
-        return Flags.enableDesktopWindowingMode();
+    public static boolean shouldDevOptionBeEnabledByDefault(Context context) {
+        return isDeviceEligibleForDesktopMode(context) && Flags.enableDesktopWindowingMode();
     }
 
     /**
      * Return {@code true} if desktop mode is enabled and can be entered on the current device.
      */
     public static boolean canEnterDesktopMode(@NonNull Context context) {
-        if (!isDeviceEligibleForDesktopMode(context)) return false;
+        return (isDeviceEligibleForDesktopMode(context)
+                && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue())
+                || isDesktopModeEnabledByDevOption(context);
+    }
 
-        return DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue();
+    /**
+     * Check if Desktop mode should be enabled because the dev option is shown and enabled.
+     */
+    private static boolean isDesktopModeEnabledByDevOption(@NonNull Context context) {
+        return DesktopModeFlags.isDesktopModeForcedEnabled()
+                && canShowDesktopModeDevOption(context);
     }
 
     /**
@@ -298,7 +314,21 @@
      * Return {@code true} if desktop mode is unrestricted and is supported in the device.
      */
     public static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) {
-        return !enforceDeviceRestrictions() || isDesktopModeSupported(context);
+        return !enforceDeviceRestrictions() || isDesktopModeSupported(context) || (
+                Flags.enableDesktopModeThroughDevOption() && isDesktopModeDevOptionSupported(
+                        context));
+    }
+
+    /**
+     * Return {@code true} if the developer option for desktop mode is unrestricted and is supported
+     * in the device.
+     *
+     * Note that, if {@link #isDeviceEligibleForDesktopMode(Context)} is true, then
+     * {@link #isDeviceEligibleForDesktopModeDevOption(Context)} is also true.
+     */
+    private static boolean isDeviceEligibleForDesktopModeDevOption(@NonNull Context context) {
+        return !enforceDeviceRestrictions() || isDesktopModeSupported(context)
+                || isDesktopModeDevOptionSupported(context);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index b09d324..10f8705 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -976,11 +976,13 @@
             cascadeWindow(bounds, displayLayout, displayId)
         }
         val pendingIntent =
-            PendingIntent.getActivity(
+            PendingIntent.getActivityAsUser(
                 context,
                 /* requestCode= */ 0,
                 intent,
                 PendingIntent.FLAG_IMMUTABLE,
+                /* options= */ null,
+                UserHandle.of(userId),
             )
         val ops =
             ActivityOptions.fromBundle(options).apply {
@@ -1523,11 +1525,16 @@
     private fun addWallpaperActivity(displayId: Int, wct: WindowContainerTransaction) {
         logV("addWallpaperActivity")
         if (ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER.isTrue()) {
+
+            // If the wallpaper activity for this display already exists, let's reorder it to top.
+            val wallpaperActivityToken = desktopWallpaperActivityTokenProvider.getToken(displayId)
+            if (wallpaperActivityToken != null) {
+                wct.reorder(wallpaperActivityToken, /* onTop= */ true)
+                return
+            }
+
             val intent = Intent(context, DesktopWallpaperActivity::class.java)
-            if (
-                desktopWallpaperActivityTokenProvider.getToken(displayId) == null &&
-                    Flags.enablePerDisplayDesktopWallpaperActivity()
-            ) {
+            if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 1eaae7e..9af2308 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -652,9 +652,16 @@
                     }
                     continue;
                 }
-                startTransaction.reparent(chg.getLeash(), tv.getSurfaceControl());
-                finishTransaction.reparent(chg.getLeash(), tv.getSurfaceControl())
-                        .setPosition(chg.getLeash(), 0, 0);
+                final Rect boundsOnScreen = tv.prepareOpen(chg.getTaskInfo(), chg.getLeash());
+                if (boundsOnScreen != null) {
+                    if (wct == null) wct = new WindowContainerTransaction();
+                    updateBounds(tv, boundsOnScreen, startTransaction, finishTransaction,
+                            chg.getTaskInfo(), chg.getLeash(), wct);
+                } else {
+                    startTransaction.reparent(chg.getLeash(), tv.getSurfaceControl());
+                    finishTransaction.reparent(chg.getLeash(), tv.getSurfaceControl())
+                            .setPosition(chg.getLeash(), 0, 0);
+                }
                 changesHandled++;
             }
         }
@@ -683,30 +690,8 @@
             WindowContainerTransaction wct) {
         final Rect boundsOnScreen = taskView.prepareOpen(taskInfo, leash);
         if (boundsOnScreen != null) {
-            final SurfaceControl tvSurface = taskView.getSurfaceControl();
-            // Surface is ready, so just reparent the task to this surface control
-            startTransaction.reparent(leash, tvSurface)
-                    .show(leash);
-            // Also reparent on finishTransaction since the finishTransaction will reparent back
-            // to its "original" parent by default.
-            if (finishTransaction != null) {
-                finishTransaction.reparent(leash, tvSurface)
-                        .setPosition(leash, 0, 0)
-                        // TODO: maybe once b/280900002 is fixed this will be unnecessary
-                        .setWindowCrop(leash, boundsOnScreen.width(), boundsOnScreen.height());
-            }
-            if (useRepo()) {
-                final TaskViewRepository.TaskViewState state = mTaskViewRepo.byTaskView(taskView);
-                if (state != null) {
-                    state.mBounds.set(boundsOnScreen);
-                    state.mVisible = true;
-                }
-            } else {
-                updateBoundsState(taskView, boundsOnScreen);
-                updateVisibilityState(taskView, true /* visible */);
-            }
-            wct.setBounds(taskInfo.token, boundsOnScreen);
-            taskView.applyCaptionInsetsIfNeeded();
+            updateBounds(taskView, boundsOnScreen, startTransaction, finishTransaction, taskInfo,
+                    leash, wct);
         } else {
             // The surface has already been destroyed before the task has appeared,
             // so go ahead and hide the task entirely
@@ -730,6 +715,36 @@
         taskView.notifyAppeared(newTask);
     }
 
+    private void updateBounds(TaskViewTaskController taskView, Rect boundsOnScreen,
+            SurfaceControl.Transaction startTransaction,
+            SurfaceControl.Transaction finishTransaction,
+            ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
+            WindowContainerTransaction wct) {
+        final SurfaceControl tvSurface = taskView.getSurfaceControl();
+        // Surface is ready, so just reparent the task to this surface control
+        startTransaction.reparent(leash, tvSurface)
+                .show(leash);
+        // Also reparent on finishTransaction since the finishTransaction will reparent back
+        // to its "original" parent by default.
+        if (finishTransaction != null) {
+            finishTransaction.reparent(leash, tvSurface)
+                    .setPosition(leash, 0, 0)
+                    .setWindowCrop(leash, boundsOnScreen.width(), boundsOnScreen.height());
+        }
+        if (useRepo()) {
+            final TaskViewRepository.TaskViewState state = mTaskViewRepo.byTaskView(taskView);
+            if (state != null) {
+                state.mBounds.set(boundsOnScreen);
+                state.mVisible = true;
+            }
+        } else {
+            updateBoundsState(taskView, boundsOnScreen);
+            updateVisibilityState(taskView, true /* visible */);
+        }
+        wct.setBounds(taskInfo.token, boundsOnScreen);
+        taskView.applyCaptionInsetsIfNeeded();
+    }
+
     /** Interface for running an external transition in this object's pending queue. */
     public interface ExternalTransition {
         /** Starts a transition and returns an identifying key for lookup. */
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index b5b7847..80e4c47a 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker.pip
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.RequiresFlagsDisabled
 import android.tools.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.flicker.legacy.FlickerBuilder
@@ -51,6 +52,7 @@
  *        apps are running before setup
  * ```
  */
+@FlakyTest(bugId = 391734110)
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
index 40b685c..4972fa9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
@@ -23,6 +23,9 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.display.DisplayManager;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.TestableContext;
 
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -31,6 +34,8 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
 import org.mockito.MockitoAnnotations;
 
 /**
@@ -38,6 +43,16 @@
  */
 public abstract class ShellTestCase {
 
+    @ClassRule
+    public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
+
     protected TestableContext mContext;
     private PackageManager mPm;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index bba9418..94dc774 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -41,7 +41,6 @@
 import android.graphics.Rect;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
 import android.window.TransitionInfo;
@@ -55,7 +54,6 @@
 import com.google.testing.junit.testparameterinjector.TestParameterInjector;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -73,9 +71,6 @@
 @RunWith(TestParameterInjector.class)
 public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnimationTestBase {
 
-    @Rule
-    public SetFlagsRule mRule = new SetFlagsRule();
-
     @Before
     public void setup() {
         super.setUp();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index 39d5507..56948d4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -34,7 +34,6 @@
 import android.graphics.Rect;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 
@@ -46,7 +45,6 @@
 import com.android.wm.shell.transition.TransitionInfoBuilder;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -64,9 +62,6 @@
     private static final Rect EMBEDDED_LEFT_BOUNDS = new Rect(0, 0, 500, 500);
     private static final Rect EMBEDDED_RIGHT_BOUNDS = new Rect(500, 0, 1000, 500);
 
-    @Rule
-    public SetFlagsRule mRule = new SetFlagsRule();
-
     @Before
     public void setup() {
         super.setUp();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index bbdb90f..e63db9a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -60,7 +60,6 @@
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.IRemoteAnimationRunner;
@@ -91,7 +90,6 @@
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -152,9 +150,6 @@
 
     private BackAnimationController.BackTransitionHandler mBackTransitionHandler;
 
-    @Rule
-    public SetFlagsRule mSetflagsRule = new SetFlagsRule();
-
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 6d7a18d..2ef6c55 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -32,6 +32,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.wm.shell.ShellTestCase;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -42,7 +44,7 @@
 @SmallTest
 @TestableLooper.RunWithLooper
 @RunWith(AndroidTestingRunner.class)
-public class BackProgressAnimatorTest {
+public class BackProgressAnimatorTest extends ShellTestCase {
     private BackProgressAnimator mProgressAnimator;
     private BackEvent mReceivedBackEvent;
     private float mTargetProgress = 0.5f;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
index f8eb50b..622e4cb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
@@ -38,6 +38,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.transition.TransitionInfoBuilder;
 
 import org.junit.Before;
@@ -49,7 +50,7 @@
  * Tests of {@link BubblesTransitionObserver}.
  */
 @SmallTest
-public class BubblesTransitionObserverTest {
+public class BubblesTransitionObserverTest extends ShellTestCase {
 
     @Mock
     private BubbleController mBubbleController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java
index f8ee300..3323740 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java
@@ -29,6 +29,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.sysui.ShellInit;
 
 import org.junit.Before;
@@ -41,7 +42,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class DevicePostureControllerTest {
+public class DevicePostureControllerTest extends ShellTestCase {
     @Mock
     private Context mContext;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 6f3a3ec..ee9d177 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -39,8 +39,6 @@
 import android.os.Looper;
 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.view.IWindowManager;
 import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
@@ -55,7 +53,6 @@
 import com.android.wm.shell.sysui.ShellInit;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -70,9 +67,6 @@
  */
 @SmallTest
 public class DisplayImeControllerTest extends ShellTestCase {
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
     @Mock
     private SurfaceControl.Transaction mT;
     @Mock
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIShellTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIShellTestCase.java
index 5a49d01..979cee9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIShellTestCase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIShellTestCase.java
@@ -16,24 +16,10 @@
 
 package com.android.wm.shell.compatui;
 
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.platform.test.flag.junit.SetFlagsRule;
-
 import com.android.wm.shell.ShellTestCase;
 
-import org.junit.Rule;
-
 /**
  * Base class for CompatUI tests.
  */
 public class CompatUIShellTestCase extends ShellTestCase {
-
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule =
-            DeviceFlagsValueProvider.createCheckFlagsRule();
-
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index 61b6d80..010474e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -38,6 +38,7 @@
 import android.app.TaskInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.testing.AndroidTestingRunner;
 import android.util.Pair;
@@ -394,8 +395,8 @@
 
     @Test
     @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
+    @EnableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON)
     public void testShouldShowSizeCompatRestartButton() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON);
         doReturn(85).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
         mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
                 mCallback, mTaskListener, mDisplayLayout, new CompatUIHintsState(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
index 319122d..d3a2c9a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
@@ -18,7 +18,6 @@
 
 
 import android.graphics.Point
-import android.platform.test.flag.junit.DeviceFlagsValueProvider
 import android.testing.AndroidTestingRunner
 import android.view.View
 import androidx.test.filters.SmallTest
@@ -29,7 +28,6 @@
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNull
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -45,9 +43,6 @@
 
     lateinit var repository: CompatUIRepository
 
-    @get:Rule
-    val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
-
     @Before
     fun setUp() {
         repository = DefaultCompatUIRepository()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
index 78bb721..008c499 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
@@ -20,7 +20,6 @@
 import android.graphics.Rect
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CLOSE
@@ -36,14 +35,12 @@
 import com.android.wm.shell.util.TransitionObserverInputBuilder
 import com.android.wm.shell.util.executeTransitionObserverTest
 import java.util.function.Consumer
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
-import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
 
 /**
@@ -56,9 +53,6 @@
 @SmallTest
 class LetterboxTransitionObserverTest : ShellTestCase() {
 
-    @get:Rule
-    val setFlagsRule: SetFlagsRule = SetFlagsRule()
-
     @Test
     @DisableFlags(Flags.FLAG_APP_COMPAT_REFACTORING)
     fun `when initialized and flag disabled the observer is not registered`() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
index 957fdf99..09ffd94 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -25,7 +25,6 @@
 import android.os.Binder
 import android.os.UserManager
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.view.Display.DEFAULT_DISPLAY
 import android.window.WindowContainerTransaction
@@ -62,7 +61,6 @@
 import kotlinx.coroutines.test.setMain
 import org.junit.After
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
@@ -89,8 +87,6 @@
 @ExperimentalCoroutinesApi
 @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_RESPECT_ORIENTATION_CHANGE_FOR_UNRESIZEABLE)
 class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
-    @JvmField @Rule val setFlagsRule = SetFlagsRule()
-
     @Mock lateinit var testExecutor: ShellExecutor
     @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
     @Mock lateinit var transitions: Transitions
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
index fae7363..0d5741f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
@@ -23,7 +23,6 @@
 import android.content.ContentResolver
 import android.os.Binder
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
 import android.provider.Settings
 import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
 import android.testing.AndroidTestingRunner
@@ -51,7 +50,6 @@
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.isNull
@@ -74,9 +72,6 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class DesktopDisplayEventHandlerTest : ShellTestCase() {
-
-    @JvmField @Rule val setFlagsRule = SetFlagsRule()
-
     @Mock lateinit var testExecutor: ShellExecutor
     @Mock lateinit var transitions: Transitions
     @Mock lateinit var displayController: DisplayController
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
index 47d133b..006c3ca 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
@@ -23,7 +23,6 @@
 import android.os.IBinder
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.Display.DEFAULT_DISPLAY
@@ -73,7 +72,6 @@
 @RunWith(AndroidTestingRunner::class)
 class DesktopImmersiveControllerTest : ShellTestCase() {
 
-    @JvmField @Rule val setFlagsRule = SetFlagsRule()
     @JvmField @Rule val animatorTestRule = AnimatorTestRule(this)
 
     @Mock private lateinit var mockTransitions: Transitions
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index f61ea4a..f48bc99 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -27,7 +27,6 @@
 import android.os.IBinder
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.SurfaceControl
@@ -57,7 +56,6 @@
 import org.junit.Assert.assertNull
 import org.junit.Assert.assertTrue
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyInt
@@ -82,8 +80,6 @@
 @RunWith(AndroidTestingRunner::class)
 class DesktopMixedTransitionHandlerTest : ShellTestCase() {
 
-    @JvmField @Rule val setFlagsRule = SetFlagsRule()
-
     @Mock lateinit var transitions: Transitions
     @Mock lateinit var userRepositories: DesktopUserRepositories
     @Mock lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler
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 bddc062..8a5acfa 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
@@ -19,7 +19,6 @@
 import android.app.ActivityManager.RunningTaskInfo
 import android.graphics.Rect
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
 import com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations
 import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
 import com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker
@@ -65,15 +64,13 @@
     val displayLayout = mock<DisplayLayout>()
 
     @JvmField
-    @Rule(order = 0)
+    @Rule()
     val extendedMockitoRule =
         ExtendedMockitoRule.Builder(this)
             .mockStatic(FrameworkStatsLog::class.java)
             .mockStatic(EventLogTags::class.java)
             .build()!!
 
-    @JvmField @Rule(order = 1) val setFlagsRule = SetFlagsRule()
-
     @Before
     fun setUp() {
         doReturn(displayLayout).whenever(displayController).getDisplayLayout(anyInt())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
index 016e040..470c110 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
@@ -24,7 +24,6 @@
 import android.hardware.input.InputManager.KeyGestureEventHandler
 import android.hardware.input.KeyGestureEvent
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.KeyEvent
@@ -64,7 +63,6 @@
 import kotlinx.coroutines.test.setMain
 import org.junit.After
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.anyInt
@@ -87,8 +85,6 @@
 @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
 class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
 
-    @JvmField @Rule val setFlagsRule = SetFlagsRule()
-
     private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
     private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
     private val focusTransitionObserver = mock<FocusTransitionObserver>()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index f5c93ee..90f342f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -20,7 +20,6 @@
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
-import android.platform.test.flag.junit.SetFlagsRule
 import android.util.ArraySet
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.Display.INVALID_DISPLAY
@@ -47,7 +46,6 @@
 import kotlinx.coroutines.test.setMain
 import org.junit.After
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
@@ -71,8 +69,6 @@
 @ExperimentalCoroutinesApi
 class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
 
-    @JvmField @Rule val setFlagsRule = SetFlagsRule(flags)
-
     private lateinit var repo: DesktopRepository
     private lateinit var shellInit: ShellInit
     private lateinit var datastoreScope: CoroutineScope
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt
index 12c7ff6..50590f0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt
@@ -18,7 +18,6 @@
 
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
@@ -26,7 +25,6 @@
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyInt
@@ -44,8 +42,6 @@
 @RunWith(AndroidTestingRunner::class)
 class DesktopTaskChangeListenerTest : ShellTestCase() {
 
-    @JvmField @Rule val setFlagsRule = SetFlagsRule()
-
     private lateinit var desktopTaskChangeListener: DesktopTaskChangeListener
 
     private val desktopUserRepositories = mock<DesktopUserRepositories>()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index a139f16..e7fe57d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -49,7 +49,6 @@
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
-import android.platform.test.flag.junit.SetFlagsRule
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.DragEvent
 import android.view.Gravity
@@ -165,7 +164,6 @@
 import org.junit.After
 import org.junit.Assume.assumeTrue
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
@@ -201,8 +199,6 @@
 @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
 class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() {
 
-    @JvmField @Rule val setFlagsRule = SetFlagsRule(flags)
-
     @Mock lateinit var testExecutor: ShellExecutor
     @Mock lateinit var shellCommandHandler: ShellCommandHandler
     @Mock lateinit var shellController: ShellController
@@ -538,6 +534,7 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperEnabled() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val task1 = setUpFreeformTask()
         val task2 = setUpFreeformTask()
         markTaskHidden(task1)
@@ -726,6 +723,7 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val task1 = setUpFreeformTask()
         val task2 = setUpFreeformTask()
         markTaskVisible(task1)
@@ -764,7 +762,8 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
-    fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperEnabled() {
+    fun showDesktopApps_someAppsInvisible_desktopWallpaperEnabled_reordersOnlyFreeformTasks() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val task1 = setUpFreeformTask()
         val task2 = setUpFreeformTask()
         markTaskHidden(task1)
@@ -781,6 +780,24 @@
         wct.assertReorderAt(index = 2, task2)
     }
 
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+    fun showDesktopApps_someAppsInvisible_desktopWallpaperEnabled_reordersAll() {
+        val task1 = setUpFreeformTask()
+        val task2 = setUpFreeformTask()
+        markTaskHidden(task1)
+        markTaskVisible(task2)
+
+        controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+        val wct =
+            getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+        assertThat(wct.hierarchyOps).hasSize(3)
+        // Expect order to be from bottom: wallpaper intent, task1, task2
+        wct.assertReorderAt(index = 0, wallpaperToken)
+        wct.assertReorderAt(index = 1, task1)
+        wct.assertReorderAt(index = 2, task2)
+    }
+
     @Test
     @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun showDesktopApps_noActiveTasks_reorderHomeToTop_desktopWallpaperDisabled() {
@@ -796,7 +813,9 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
-    fun showDesktopApps_noActiveTasks_addDesktopWallpaper_desktopWallpaperEnabled() {
+    fun showDesktopApps_noActiveTasks_desktopWallpaperEnabled_addsDesktopWallpaper() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+
         controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
 
         val wct =
@@ -805,6 +824,16 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+    fun showDesktopApps_noActiveTasks_desktopWallpaperEnabled_reordersDesktopWallpaper() {
+        controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+        val wct =
+            getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+        wct.assertReorderAt(index = 0, wallpaperToken)
+    }
+
+    @Test
     @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() {
         taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
@@ -828,6 +857,7 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
         val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
         val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
@@ -872,6 +902,7 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val homeTask = setUpHomeTask()
         val freeformTask = setUpFreeformTask()
         val minimizedTask = setUpFreeformTask()
@@ -1337,6 +1368,7 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun moveTaskToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val task = createTaskInfo(1)
         whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
         whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
@@ -1484,6 +1516,7 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperEnabled() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val freeformTask = setUpFreeformTask()
         val fullscreenTask = setUpFullscreenTask()
         markTaskHidden(freeformTask)
@@ -1586,6 +1619,7 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun moveRunningTaskToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
         val newTask = setUpFullscreenTask()
         val homeTask = setUpHomeTask()
@@ -2606,6 +2640,7 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
         val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
         tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
@@ -2737,6 +2772,7 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val freeformTask1 = setUpFreeformTask()
         val freeformTask2 = createFreeformTask()
 
@@ -2771,7 +2807,9 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val task = createFreeformTask()
+
         val result = controller.handleRequest(Binder(), createTransition(task))
 
         assertNotNull(result, "Should handle request")
@@ -2799,6 +2837,7 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
     fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() {
+        whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
         val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
         // Second display task
         createFreeformTask(displayId = SECOND_DISPLAY)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 554b09f..d33209d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -24,7 +24,6 @@
 import android.os.UserManager
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.SurfaceControl
@@ -66,7 +65,6 @@
 import kotlinx.coroutines.test.setMain
 import org.junit.After
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
@@ -88,8 +86,6 @@
 @ExperimentalCoroutinesApi
 class DesktopTasksLimiterTest : ShellTestCase() {
 
-    @JvmField @Rule val setFlagsRule = SetFlagsRule()
-
     @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
     @Mock lateinit var transitions: Transitions
     @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index ca1e3ed..091159c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -25,7 +25,6 @@
 import android.os.Binder
 import android.os.IBinder
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.WindowManager
 import android.view.WindowManager.TRANSIT_CLOSE
@@ -77,9 +76,6 @@
  * Build/Install/Run: atest WMShellUnitTests:DesktopTasksTransitionObserverTest
  */
 class DesktopTasksTransitionObserverTest {
-
-    @JvmField @Rule val setFlagsRule = SetFlagsRule()
-
     @JvmField
     @Rule
     val extendedMockitoRule =
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
index b9e307fa5..83e4872 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
@@ -21,7 +21,6 @@
 import android.os.UserManager
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
@@ -44,7 +43,6 @@
 import kotlinx.coroutines.test.setMain
 import org.junit.After
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.spy
@@ -56,8 +54,6 @@
 @RunWith(AndroidTestingRunner::class)
 @ExperimentalCoroutinesApi
 class DesktopUserRepositoriesTest : ShellTestCase() {
-    @get:Rule val setFlagsRule = SetFlagsRule()
-
     private lateinit var userRepositories: DesktopUserRepositories
     private lateinit var shellInit: ShellInit
     private lateinit var datastoreScope: CoroutineScope
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
index 1160a92..86e8142 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
@@ -19,7 +19,6 @@
 import android.os.SystemProperties
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.testing.TestableContext
 import androidx.test.filters.SmallTest
@@ -75,7 +74,6 @@
             .mockStatic(DesktopModeStatus::class.java)
             .mockStatic(SystemProperties::class.java)
             .build()!!
-    @JvmField @Rule val setFlagsRule = SetFlagsRule()
 
     private lateinit var educationController: AppHandleEducationController
     private lateinit var testableContext: TestableContext
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
index 9a8f264..dd9e6ca 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
@@ -19,7 +19,6 @@
 import android.os.UserManager
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.view.Display.DEFAULT_DISPLAY
 import androidx.test.filters.SmallTest
@@ -42,7 +41,6 @@
 import kotlinx.coroutines.test.setMain
 import org.junit.After
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.spy
@@ -54,8 +52,6 @@
 @ExperimentalCoroutinesApi
 class DesktopRepositoryInitializerTest : ShellTestCase() {
 
-    @JvmField @Rule val setFlagsRule = SetFlagsRule()
-
     private lateinit var repositoryInitializer: DesktopRepositoryInitializer
     private lateinit var shellInit: ShellInit
     private lateinit var datastoreScope: CoroutineScope
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
index 4174bbd..9509aaf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -35,7 +35,6 @@
 import android.app.ActivityManager;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.SurfaceControl;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -56,7 +55,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -72,9 +70,6 @@
 @RunWith(AndroidJUnit4.class)
 public final class FreeformTaskListenerTests extends ShellTestCase {
 
-    @Rule
-    public final SetFlagsRule setFlagsRule = new SetFlagsRule();
-
     @Mock
     private ShellTaskOrganizer mTaskOrganizer;
     @Mock
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
index 5aed461..bc91845 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -34,7 +34,6 @@
 import android.content.pm.PackageManager;
 import android.os.IBinder;
 import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.SurfaceControl;
 import android.window.IWindowContainerToken;
 import android.window.TransitionInfo;
@@ -43,6 +42,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.window.flags.Flags;
+import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.desktopmode.DesktopImmersiveController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.FocusTransitionObserver;
@@ -60,9 +60,8 @@
 
 /** Tests for {@link FreeformTaskTransitionObserver}. */
 @SmallTest
-public class FreeformTaskTransitionObserverTest {
+public class FreeformTaskTransitionObserverTest extends ShellTestCase {
 
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     @Mock private ShellInit mShellInit;
     @Mock private Transitions mTransitions;
     @Mock private DesktopImmersiveController mDesktopImmersiveController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 836f4c2..2668823 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -37,7 +37,6 @@
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.platform.test.annotations.DisableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Rational;
@@ -70,8 +69,6 @@
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentMatchers;
@@ -88,11 +85,6 @@
 @TestableLooper.RunWithLooper
 @DisableFlags(Flags.FLAG_ENABLE_PIP2)
 public class PipTaskOrganizerTest extends ShellTestCase {
-    @ClassRule
-    public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
-
     private PipTaskOrganizer mPipTaskOrganizer;
 
     @Mock private DisplayController mMockDisplayController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 5ef934c..13fce2a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -43,7 +43,6 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.platform.test.annotations.DisableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -77,8 +76,6 @@
 import com.android.wm.shell.sysui.ShellInit;
 
 import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -95,9 +92,6 @@
 @TestableLooper.RunWithLooper
 @DisableFlags(Flags.FLAG_ENABLE_PIP2)
 public class PipControllerTest extends ShellTestCase {
-    @ClassRule public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
-    @Rule public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
-
     private PipController mPipController;
     private ShellInit mShellInit;
     private ShellController mShellController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index b11715b..273cb27 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -25,7 +25,6 @@
 
 import android.graphics.Rect;
 import android.platform.test.annotations.DisableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Size;
@@ -49,7 +48,6 @@
 import com.android.wm.shell.sysui.ShellInit;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -68,9 +66,6 @@
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class PipTouchHandlerTest extends ShellTestCase {
-    @Rule
-    public SetFlagsRule setFlagsRule = new SetFlagsRule();
-
     private static final int INSET = 10;
     private static final int PIP_LENGTH = 100;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 542289d..5028479 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -63,7 +63,6 @@
 import android.os.Bundle;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.SurfaceControl;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -89,7 +88,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -129,9 +127,6 @@
     @Mock
     private DesktopRepository mDesktopRepository;
 
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
     private ShellTaskOrganizer mShellTaskOrganizer;
     private RecentTasksController mRecentTasksController;
     private RecentTasksController mRecentTasksControllerReal;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
index ab43119..b50af74 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -47,7 +47,6 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 
@@ -77,7 +76,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -115,9 +113,6 @@
 
     @Mock private DesktopRepository mDesktopRepository;
 
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
     private ShellTaskOrganizer mShellTaskOrganizer;
     private RecentTasksController mRecentTasksController;
     private RecentTasksController mRecentTasksControllerReal;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
index 9919462..769407b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
@@ -26,7 +26,6 @@
 import android.os.IBinder
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.view.SurfaceControl
 import android.view.WindowManager
@@ -42,6 +41,7 @@
 import androidx.test.filters.SmallTest
 import com.android.window.flags.Flags
 import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestShellExecutor
 import com.android.wm.shell.TestSyncExecutor
 import com.android.wm.shell.common.ShellExecutor
@@ -53,7 +53,6 @@
 import com.google.common.truth.Truth.assertThat
 import dagger.Lazy
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
@@ -71,9 +70,7 @@
  */
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
-class TaskStackTransitionObserverTest {
-
-    @JvmField @Rule val setFlagsRule = SetFlagsRule()
+class TaskStackTransitionObserverTest : ShellTestCase() {
 
     @Mock private lateinit var shellInit: ShellInit
     @Mock private lateinit var shellTaskOrganizerLazy: Lazy<ShellTaskOrganizer>
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
new file mode 100644
index 0000000..4dac99b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.shared.desktopmode
+
+import android.content.Context
+import android.content.res.Resources
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
+import android.provider.Settings
+import android.provider.Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES
+import android.window.DesktopModeFlags
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@Presubmit
+@EnableFlags(Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+class DesktopModeStatusTest : ShellTestCase() {
+    @get:Rule
+    val mSetFlagsRule = SetFlagsRule()
+
+    private val mockContext = mock<Context>()
+    private val mockResources = mock<Resources>()
+
+    @Before
+    fun setUp() {
+        doReturn(mockResources).whenever(mockContext).getResources()
+        doReturn(false).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
+        doReturn(false).whenever(mockResources).getBoolean(
+            eq(R.bool.config_isDesktopModeDevOptionSupported)
+        )
+        doReturn(context.contentResolver).whenever(mockContext).contentResolver
+        resetDesktopModeFlagsCache()
+        resetEnforceDeviceRestriction()
+        resetFlagOverride()
+    }
+
+    @After
+    fun tearDown() {
+        resetDesktopModeFlagsCache()
+        resetEnforceDeviceRestriction()
+        resetFlagOverride()
+    }
+
+    @DisableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+        Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
+    )
+    @Test
+    fun canEnterDesktopMode_DWFlagDisabled_configsOff_returnsFalse() {
+        assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
+    }
+
+    @DisableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+        Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
+    )
+    @Test
+    fun canEnterDesktopMode_DWFlagDisabled_configsOn_disableDeviceRestrictions_returnsFalse() {
+        doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
+        doReturn(true).whenever(mockResources).getBoolean(
+            eq(R.bool.config_isDesktopModeDevOptionSupported)
+        )
+        disableEnforceDeviceRestriction()
+
+        assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
+    }
+
+    @DisableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+        Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
+    )
+    @Test
+    fun canEnterDesktopMode_DWFlagDisabled_configDevOptionOn_returnsFalse() {
+        doReturn(true).whenever(mockResources).getBoolean(
+            eq(R.bool.config_isDesktopModeDevOptionSupported)
+        )
+
+        assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
+    }
+
+    @DisableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+        Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
+    )
+    @Test
+    fun canEnterDesktopMode_DWFlagDisabled_configDevOptionOn_flagOverrideOn_returnsTrue() {
+        doReturn(true).whenever(mockResources).getBoolean(
+            eq(R.bool.config_isDesktopModeDevOptionSupported)
+        )
+        setFlagOverride(DesktopModeFlags.ToggleOverride.OVERRIDE_ON)
+
+        assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isTrue()
+    }
+
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    @Test
+    fun canEnterDesktopMode_DWFlagEnabled_configsOff_returnsFalse() {
+        assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
+    }
+
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    @Test
+    fun canEnterDesktopMode_DWFlagEnabled_configDesktopModeOff_returnsFalse() {
+        doReturn(true).whenever(mockResources).getBoolean(
+            eq(R.bool.config_isDesktopModeDevOptionSupported)
+        )
+
+        assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
+    }
+
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    @Test
+    fun canEnterDesktopMode_DWFlagEnabled_configDesktopModeOn_returnsTrue() {
+        doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
+
+        assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isTrue()
+    }
+
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    @Test
+    fun canEnterDesktopMode_DWFlagEnabled_configsOff_disableDeviceRestrictions_returnsTrue() {
+        disableEnforceDeviceRestriction()
+
+        assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isTrue()
+    }
+
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    @Test
+    fun canEnterDesktopMode_DWFlagEnabled_configDevOptionOn_flagOverrideOn_returnsTrue() {
+        doReturn(true).whenever(mockResources).getBoolean(
+            eq(R.bool.config_isDesktopModeDevOptionSupported)
+        )
+        setFlagOverride(DesktopModeFlags.ToggleOverride.OVERRIDE_ON)
+
+        assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isTrue()
+    }
+
+    @Test
+    fun isDeviceEligibleForDesktopMode_configDEModeOn_returnsTrue() {
+        doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
+
+        assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isTrue()
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+    @Test
+    fun isDeviceEligibleForDesktopMode_supportFlagOff_returnsFalse() {
+        assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse()
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+    @Test
+    fun isDeviceEligibleForDesktopMode_supportFlagOn_returnsFalse() {
+        assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse()
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+    @Test
+    fun isDeviceEligibleForDesktopMode_supportFlagOn_configDevOptModeOn_returnsTrue() {
+        doReturn(true).whenever(mockResources).getBoolean(
+            eq(R.bool.config_isDesktopModeDevOptionSupported)
+        )
+
+        assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isTrue()
+    }
+
+    private fun resetEnforceDeviceRestriction() {
+        setEnforceDeviceRestriction(true)
+    }
+
+    private fun disableEnforceDeviceRestriction() {
+        setEnforceDeviceRestriction(false)
+    }
+
+    private fun setEnforceDeviceRestriction(value: Boolean) {
+        val field = DesktopModeStatus::class.java.getDeclaredField("ENFORCE_DEVICE_RESTRICTIONS")
+        field.isAccessible = true
+        field.setBoolean(null, value)
+    }
+
+    private fun resetDesktopModeFlagsCache() {
+        val cachedToggleOverride =
+            DesktopModeFlags::class.java.getDeclaredField("sCachedToggleOverride")
+        cachedToggleOverride.isAccessible = true
+        cachedToggleOverride.set(null, null)
+    }
+
+    private fun resetFlagOverride() {
+        Settings.Global.putString(
+            context.contentResolver,
+            DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, null
+        )
+    }
+
+    private fun setFlagOverride(override: DesktopModeFlags.ToggleOverride) {
+        Settings.Global.putInt(
+            context.contentResolver,
+            DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, override.setting
+        )
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 6ac34d7..2d454a5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -51,7 +51,6 @@
 import android.graphics.Region;
 import android.os.Looper;
 import android.platform.test.flag.junit.FlagsParameterization;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.TestableLooper;
 import android.view.SurfaceControl;
 import android.view.SurfaceHolder;
@@ -72,7 +71,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -96,9 +94,6 @@
         return FlagsParameterization.allCombinationsOf(Flags.FLAG_TASK_VIEW_REPOSITORY);
     }
 
-    @Rule
-    public final SetFlagsRule mSetFlagsRule;
-
     @Mock
     TaskView.Listener mViewListener;
     @Mock
@@ -127,9 +122,7 @@
     TaskViewTransitions mTaskViewTransitions;
     TaskViewTaskController mTaskViewTaskController;
 
-    public TaskViewTest(FlagsParameterization flags) {
-        mSetFlagsRule = new SetFlagsRule(flags);
-    }
+    public TaskViewTest(FlagsParameterization flags) {}
 
     @Before
     public void setUp() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
index 326f11e..3a455ba 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
@@ -34,7 +34,6 @@
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.platform.test.flag.junit.FlagsParameterization;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.TestableLooper;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
@@ -50,7 +49,6 @@
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -74,9 +72,6 @@
                 Flags.FLAG_ENABLE_TASK_VIEW_CONTROLLER_CLEANUP);
     }
 
-    @Rule
-    public final SetFlagsRule mSetFlagsRule;
-
     @Mock
     Transitions mTransitions;
     @Mock
@@ -95,9 +90,7 @@
     TaskViewRepository mTaskViewRepository;
     TaskViewTransitions mTaskViewTransitions;
 
-    public TaskViewTransitionsTest(FlagsParameterization flags) {
-        mSetFlagsRule = new SetFlagsRule(flags);
-    }
+    public TaskViewTransitionsTest(FlagsParameterization flags) {}
 
     @Before
     public void setUp() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java
index 74c2f0e..96e4f49 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java
@@ -35,8 +35,6 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.os.RemoteException;
 import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 import android.window.TransitionInfo.TransitionMode;
@@ -50,7 +48,6 @@
 import com.android.wm.shell.shared.FocusTransitionListener;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -67,8 +64,6 @@
 
     static final int SECONDARY_DISPLAY_ID = 1;
 
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
     private FocusTransitionListener mListener;
     private final TestShellExecutor mShellExecutor = new TestShellExecutor();
     private FocusTransitionObserver mFocusTransitionObserver;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index 3e53ee5..6f28e656 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -39,8 +39,6 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 import android.window.TransitionInfo.TransitionMode;
@@ -60,7 +58,6 @@
 import com.android.wm.shell.sysui.ShellInit;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -73,9 +70,6 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class HomeTransitionObserverTest extends ShellTestCase {
-
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
     private final ShellTaskOrganizer mOrganizer = mock(ShellTaskOrganizer.class);
     private final TransactionPool mTransactionPool = mock(TransactionPool.class);
     private final Context mContext =
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 0a19be4..efbc533 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -74,7 +74,8 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
-import android.platform.test.flag.junit.SetFlagsRule;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.util.ArraySet;
 import android.util.Pair;
 import android.view.Surface;
@@ -117,7 +118,6 @@
 import com.android.wm.shell.util.StubTransaction;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
@@ -145,9 +145,6 @@
     private final TestTransitionHandler mDefaultHandler = new TestTransitionHandler();
     private final Handler mMainHandler = new Handler(Looper.getMainLooper());
 
-    @Rule
-    public final SetFlagsRule setFlagsRule = new SetFlagsRule();
-
     @Before
     public void setUp() {
         doAnswer(invocation -> new Binder())
@@ -553,7 +550,8 @@
     }
 
     @Test
-    public void testRegisteredRemoteTransitionTakeover() {
+    @DisableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
+    public void testRegisteredRemoteTransitionTakeover_flagDisabled() {
         Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
 
@@ -608,7 +606,6 @@
         mMainExecutor.flushAll();
 
         // Takeover shouldn't happen when the flag is disabled.
-        setFlagsRule.disableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED);
         IBinder transitToken = new Binder();
         transitions.requestStartTransition(transitToken,
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
@@ -621,12 +618,69 @@
         mDefaultHandler.finishAll();
         mMainExecutor.flushAll();
         verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
+    public void testRegisteredRemoteTransitionTakeover_flagEnabled() {
+        Transitions transitions = createTestTransitions();
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        IRemoteTransition testRemote = new RemoteTransitionStub() {
+            @Override
+            public void startAnimation(IBinder token, TransitionInfo info,
+                    SurfaceControl.Transaction t,
+                    IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
+                final Transitions.TransitionHandler takeoverHandler =
+                        transitions.getHandlerForTakeover(token, info);
+
+                if (takeoverHandler == null) {
+                    finishCallback.onTransitionFinished(null /* wct */, null /* sct */);
+                    return;
+                }
+
+                takeoverHandler.takeOverAnimation(token, info, new SurfaceControl.Transaction(),
+                        wct -> {
+                            try {
+                                finishCallback.onTransitionFinished(wct, null /* sct */);
+                            } catch (RemoteException e) {
+                                // Fail
+                            }
+                        }, new WindowAnimationState[info.getChanges().size()]);
+            }
+        };
+        final boolean[] takeoverRemoteCalled = new boolean[]{false};
+        IRemoteTransition testTakeoverRemote = new RemoteTransitionStub() {
+            @Override
+            public void startAnimation(IBinder token, TransitionInfo info,
+                    SurfaceControl.Transaction t,
+                    IRemoteTransitionFinishedCallback finishCallback) {}
+
+            @Override
+            public void takeOverAnimation(IBinder transition, TransitionInfo info,
+                    SurfaceControl.Transaction startTransaction,
+                    IRemoteTransitionFinishedCallback finishCallback, WindowAnimationState[] states)
+                    throws RemoteException {
+                takeoverRemoteCalled[0] = true;
+                finishCallback.onTransitionFinished(null /* wct */, null /* sct */);
+            }
+        };
+
+        TransitionFilter filter = new TransitionFilter();
+        filter.mRequirements =
+                new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()};
+        filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
+
+        transitions.registerRemote(filter, new RemoteTransition(testRemote, "Test"));
+        transitions.registerRemoteForTakeover(
+                filter, new RemoteTransition(testTakeoverRemote, "Test"));
+        mMainExecutor.flushAll();
 
         // Takeover should happen when the flag is enabled.
-        setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED);
+        IBinder transitToken = new Binder();
         transitions.requestStartTransition(transitToken,
                 new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
-        info = new TransitionInfoBuilder(TRANSIT_OPEN)
+        TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
                 .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
         transitions.onTransitionReady(transitToken, info, new StubTransaction(),
                 new StubTransaction());
@@ -634,7 +688,7 @@
         assertTrue(takeoverRemoteCalled[0]);
         mDefaultHandler.finishAll();
         mMainExecutor.flushAll();
-        verify(mOrganizer, times(2)).finishTransition(eq(transitToken), any());
+        verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any());
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
index 71af97e..5ba2f18 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -43,6 +43,7 @@
 import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
 
+import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestSyncExecutor;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.shared.TransactionPool;
@@ -61,7 +62,7 @@
 import java.util.List;
 import java.util.concurrent.Executor;
 
-public class UnfoldTransitionHandlerTest {
+public class UnfoldTransitionHandlerTest extends ShellTestCase {
 
     private UnfoldTransitionHandler mUnfoldTransitionHandler;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt
index cf6c3a5e..257bbb5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt
@@ -19,7 +19,6 @@
 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.SurfaceControl
@@ -35,7 +34,6 @@
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.mock
@@ -51,10 +49,6 @@
 @RunWith(AndroidTestingRunner::class)
 class DesktopHeaderManageWindowsMenuTest : ShellTestCase() {
 
-    @JvmField
-    @Rule
-    val setFlagsRule: SetFlagsRule = SetFlagsRule()
-
     private lateinit var userRepositories: DesktopUserRepositories
     private lateinit var menu: DesktopHeaderManageWindowsMenu
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index c5c8274..541b19cf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -25,9 +25,6 @@
 import android.hardware.input.InputManager
 import android.os.Handler
 import android.os.UserHandle
-import android.platform.test.flag.junit.CheckFlagsRule
-import android.platform.test.flag.junit.DeviceFlagsValueProvider
-import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.TestableContext
 import android.util.SparseArray
 import android.view.Choreographer
@@ -84,7 +81,6 @@
 import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier
 import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder
 import org.junit.After
-import org.junit.Rule
 import org.mockito.Mockito
 import org.mockito.Mockito.anyInt
 import org.mockito.kotlin.any
@@ -105,14 +101,6 @@
  */
 @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
 open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
-    @JvmField
-    @Rule
-    val setFlagsRule = SetFlagsRule()
-
-    @JvmField
-    @Rule
-    val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
-
     private val mockDesktopModeWindowDecorFactory = mock<DesktopModeWindowDecoration.Factory>()
     protected val mockMainHandler = mock<Handler>()
     protected val mockMainChoreographer = mock<Choreographer>()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 76e1e80..18a780b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -20,7 +20,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
 import static android.view.WindowInsets.Type.captionBar;
@@ -69,7 +68,6 @@
 import android.os.SystemProperties;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.testing.TestableLooper;
@@ -129,7 +127,6 @@
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Ignore;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -171,8 +168,6 @@
     private static final boolean DEFAULT_HAS_GLOBAL_FOCUS = true;
     private static final boolean DEFAULT_SHOULD_IGNORE_CORNER_RADIUS = false;
 
-    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
-
     @Mock
     private DisplayController mMockDisplayController;
     @Mock
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index a20a89c..ab9dbc9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -23,7 +23,6 @@
 import android.os.IBinder
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.view.Display
 import android.window.WindowContainerToken
@@ -44,7 +43,6 @@
 import junit.framework.Assert.assertTrue
 import org.junit.After
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
@@ -85,10 +83,6 @@
     @Mock
     private lateinit var mockResources: Resources
 
-    @JvmField
-    @Rule
-    val setFlagsRule = SetFlagsRule()
-
     private lateinit var mockitoSession: StaticMockitoSession
 
     @Before
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
index 479f156..3389ec1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -30,7 +30,6 @@
 import android.graphics.Region;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.util.Size;
 
@@ -41,7 +40,6 @@
 
 import com.google.common.testing.EqualsTester;
 
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -87,9 +85,6 @@
     private static final Point BOTTOM_INSET_POINT = new Point(TASK_SIZE.getWidth() / 2,
             TASK_SIZE.getHeight() - EDGE_RESIZE_HANDLE_INSET / 2);
 
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
     /**
      * Check that both groups of objects satisfy equals/hashcode within each group, and that each
      * group is distinct from the next.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index f90988e..f984f6d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -26,7 +26,6 @@
 import android.graphics.Rect
 import android.graphics.drawable.BitmapDrawable
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.Display
@@ -60,7 +59,6 @@
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyInt
@@ -81,10 +79,6 @@
 @TestableLooper.RunWithLooper
 @RunWith(AndroidTestingRunner::class)
 class HandleMenuTest : ShellTestCase() {
-    @JvmField
-    @Rule
-    val setFlagsRule: SetFlagsRule = SetFlagsRule()
-
     @Mock
     private lateinit var mockDesktopWindowDecoration: DesktopModeWindowDecoration
     @Mock
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index d969346..3a8dcd6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -18,7 +18,6 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
 import static android.view.WindowInsets.Type.captionBar;
@@ -62,7 +61,6 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Handler;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.util.DisplayMetrics;
 import android.view.AttachedSurfaceControl;
@@ -93,7 +91,6 @@
 import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -120,9 +117,6 @@
     private static final int SHADOW_RADIUS = 10;
     private static final int STATUS_BAR_INSET_SOURCE_ID = 0;
 
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
-
     private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
             new WindowDecoration.RelayoutResult<>();
 
diff --git a/media/java/android/media/OWNERS b/media/java/android/media/OWNERS
index b6096a1..a600017 100644
--- a/media/java/android/media/OWNERS
+++ b/media/java/android/media/OWNERS
@@ -1,6 +1,7 @@
 # Bug component: 1344
-fgoldfain@google.com
+pshehane@google.com
 elaurent@google.com
+etalvala@google.com
 lajos@google.com
 jmtrivi@google.com
 
diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java
index e558209..e4de3e4 100644
--- a/media/java/android/media/quality/MediaQualityContract.java
+++ b/media/java/android/media/quality/MediaQualityContract.java
@@ -341,6 +341,13 @@
         public static final String PARAMETER_FILM_MODE = "film_mode";
 
         /**
+         * Enable/disable black color auto stretch
+         *
+         * @hide
+         */
+        public static final String PARAMETER_BLACK_STRETCH = "black_stretch";
+
+        /**
          * Enable/disable blue color auto stretch
          *
          * <p>Type: BOOLEAN
@@ -457,6 +464,27 @@
          * @hide
          *
          */
+        public static final String PARAMETER_COLOR_TEMPERATURE_RED_GAIN =
+                "color_temperature_red_gain";
+
+        /**
+         * @hide
+         *
+         */
+        public static final String PARAMETER_COLOR_TEMPERATURE_GREEN_GAIN =
+                "color_temperature_green_gain";
+
+        /**
+         * @hide
+         *
+         */
+        public static final String PARAMETER_COLOR_TEMPERATURE_BLUE_GAIN =
+                "color_temperature_blue_gain";
+
+        /**
+         * @hide
+         *
+         */
         public static final String PARAMETER_COLOR_TEMPERATURE_RED_OFFSET =
                 "color_temperature_red_offset";
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
index 292aa51..6d30f49 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
@@ -43,6 +43,7 @@
                     "netd_native",
                     "network_security",
                     "on_device_personalization",
+                    "testing",
                     "tethering",
                     "tethering_u_or_later_native",
                     "thread_network"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index dace50f..bb0d5d7 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -196,6 +196,18 @@
 }
 
 flag {
+    name: "notification_undo_guts_on_config_changed"
+    namespace: "systemui"
+    description: "Fixes a bug where a theme or font change while notification guts were open"
+        " (e.g. the snooze options or notification info) would show an empty notification by"
+        " closing the guts and undoing changes."
+    bug: "379267630"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
    name: "pss_app_selector_recents_split_screen"
    namespace: "systemui"
    description: "Allows recent apps selected for partial screenshare to be launched in split screen mode"
@@ -575,20 +587,6 @@
 }
 
 flag {
-   name: "clock_reactive_variants"
-   namespace: "systemui"
-   description: "Add reactive variant fonts to some clocks"
-   bug: "343495953"
-}
-
-flag {
-   name: "lockscreen_custom_clocks"
-   namespace: "systemui"
-   description: "Enable lockscreen custom clocks"
-   bug: "378486437"
-}
-
-flag {
    name: "faster_unlock_transition"
    namespace: "systemui"
    description: "Faster wallpaper unlock transition"
@@ -1828,6 +1826,13 @@
 }
 
 flag {
+    name: "notification_row_transparency"
+    namespace: "systemui"
+    description: "Enables transparency on the Notification Shade."
+    bug: "392187268"
+}
+
+flag {
   name: "shade_expands_on_status_bar_long_press"
   namespace: "systemui"
   description: "Expands the shade on long press of any status bar"
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
index 0f6e6a7..f490968b 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
@@ -34,7 +34,6 @@
 import dagger.Provides
 import dagger.multibindings.IntoSet
 import javax.inject.Provider
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 @Module(includes = [LockscreenSceneBlueprintModule::class])
 interface LockscreenSceneModule {
@@ -43,7 +42,6 @@
 
     companion object {
 
-        @OptIn(ExperimentalCoroutinesApi::class)
         @Provides
         @SysUISingleton
         @KeyguardRootView
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 30dfa5b..31aebc2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -156,14 +156,8 @@
 
                 val bottomAreaPlaceable = bottomAreaMeasurable.measure(noMinConstraints)
 
-                val screensaverButtonSizeInt = screensaverButtonSize.roundToPx()
                 val screensaverButtonPlaceable =
-                    screensaverButtonMeasurable?.measure(
-                        Constraints.fixed(
-                            width = screensaverButtonSizeInt,
-                            height = screensaverButtonSizeInt,
-                        )
-                    )
+                    screensaverButtonMeasurable?.measure(noMinConstraints)
 
                 val communalGridPlaceable =
                     communalGridMeasurable.measure(
@@ -181,12 +175,12 @@
                     screensaverButtonPlaceable?.place(
                         x =
                             constraints.maxWidth -
-                                screensaverButtonSizeInt -
-                                screensaverButtonPaddingInt,
+                                screensaverButtonPaddingInt -
+                                screensaverButtonPlaceable.width,
                         y =
                             constraints.maxHeight -
-                                screensaverButtonSizeInt -
-                                screensaverButtonPaddingInt,
+                                screensaverButtonPaddingInt -
+                                screensaverButtonPlaceable.height,
                     )
                 }
             }
@@ -194,7 +188,6 @@
     }
 
     companion object {
-        private val screensaverButtonSize: Dp = 64.dp
         private val screensaverButtonPadding: Dp = 24.dp
 
         // TODO(b/382739998): Remove these hardcoded values once lock icon size and bottom area
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalToDreamButtonSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalToDreamButtonSection.kt
index 9421596..13d551a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalToDreamButtonSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalToDreamButtonSection.kt
@@ -16,14 +16,37 @@
 
 package com.android.systemui.communal.ui.compose.section
 
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.shape.CornerSize
 import androidx.compose.material3.IconButtonDefaults
 import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.RoundRect
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.geometry.toRect
+import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.res.stringResource
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
 import com.android.compose.PlatformIconButton
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.communal.ui.compose.extensions.observeTaps
 import com.android.systemui.communal.ui.viewmodel.CommunalToDreamButtonViewModel
 import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.res.R
@@ -43,23 +66,111 @@
 
         val viewModel =
             rememberViewModel("CommunalToDreamButtonSection") { viewModelFactory.create() }
-        val shouldShowDreamButtonOnHub by
-            viewModel.shouldShowDreamButtonOnHub.collectAsStateWithLifecycle(false)
 
-        if (!shouldShowDreamButtonOnHub) {
+        if (!viewModel.shouldShowDreamButtonOnHub) {
             return
         }
 
-        PlatformIconButton(
-            onClick = { viewModel.onShowDreamButtonTap() },
-            iconResource = R.drawable.ic_screensaver_auto,
-            contentDescription =
-                stringResource(R.string.accessibility_glanceable_hub_to_dream_button),
-            colors =
-                IconButtonDefaults.filledIconButtonColors(
-                    contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
-                    containerColor = MaterialTheme.colorScheme.primaryContainer,
-                ),
+        if (viewModel.shouldShowTooltip) {
+            Column(
+                modifier =
+                    Modifier.widthIn(max = tooltipMaxWidth).pointerInput(Unit) {
+                        observeTaps { viewModel.setDreamButtonTooltipDismissed() }
+                    }
+            ) {
+                Tooltip(
+                    pointerOffsetDp = buttonSize.div(2),
+                    text = stringResource(R.string.glanceable_hub_to_dream_button_tooltip),
+                )
+                GoToDreamButton(
+                    modifier = Modifier.width(buttonSize).height(buttonSize).align(Alignment.End)
+                ) {
+                    viewModel.onShowDreamButtonTap()
+                }
+            }
+        } else {
+            GoToDreamButton(modifier = Modifier.width(buttonSize).height(buttonSize)) {
+                viewModel.onShowDreamButtonTap()
+            }
+        }
+    }
+
+    companion object {
+        private val buttonSize = 64.dp
+        private val tooltipMaxWidth = 350.dp
+    }
+}
+
+@Composable
+private fun GoToDreamButton(modifier: Modifier, onClick: () -> Unit) {
+    PlatformIconButton(
+        modifier = modifier,
+        onClick = onClick,
+        iconResource = R.drawable.ic_screensaver_auto,
+        contentDescription = stringResource(R.string.accessibility_glanceable_hub_to_dream_button),
+        colors =
+            IconButtonDefaults.filledIconButtonColors(
+                contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+                containerColor = MaterialTheme.colorScheme.primaryContainer,
+            ),
+    )
+}
+
+@Composable
+private fun Tooltip(pointerOffsetDp: Dp, text: String) {
+    Surface(
+        color = MaterialTheme.colorScheme.surface,
+        shape = TooltipShape(pointerSizeDp = 12.dp, pointerOffsetDp = pointerOffsetDp),
+    ) {
+        Text(
+            modifier = Modifier.padding(start = 32.dp, top = 16.dp, end = 32.dp, bottom = 32.dp),
+            color = MaterialTheme.colorScheme.onSurface,
+            text = text,
         )
     }
+
+    Spacer(modifier = Modifier.height(4.dp))
+}
+
+private class TooltipShape(private val pointerSizeDp: Dp, private val pointerOffsetDp: Dp) : Shape {
+
+    override fun createOutline(
+        size: Size,
+        layoutDirection: LayoutDirection,
+        density: Density,
+    ): Outline {
+
+        val pointerSizePx = with(density) { pointerSizeDp.toPx() }
+        val pointerOffsetPx = with(density) { pointerOffsetDp.toPx() }
+        val cornerRadius = CornerRadius(CornerSize(16.dp).toPx(size, density))
+        val bubbleSize = size.copy(height = size.height - pointerSizePx)
+
+        val path =
+            Path().apply {
+                addRoundRect(
+                    RoundRect(
+                        rect = bubbleSize.toRect(),
+                        topLeft = cornerRadius,
+                        topRight = cornerRadius,
+                        bottomRight = cornerRadius,
+                        bottomLeft = cornerRadius,
+                    )
+                )
+                addPath(
+                    Path().apply {
+                        moveTo(0f, 0f)
+                        lineTo(pointerSizePx / 2f, pointerSizePx)
+                        lineTo(pointerSizePx, 0f)
+                        close()
+                    },
+                    offset =
+                        Offset(
+                            x = bubbleSize.width - pointerOffsetPx - pointerSizePx / 2f,
+                            y = bubbleSize.height,
+                        ),
+                )
+            }
+
+        return Outline.Generic(path)
+    }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt
index d022150..500527f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt
@@ -57,9 +57,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
 import com.android.systemui.log.LongPressHandlingViewLogger
 import com.android.systemui.res.R
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-@ExperimentalCoroutinesApi
 @Composable
 fun AlternateBouncer(
     alternateBouncerDependencies: AlternateBouncerDependencies,
@@ -127,7 +125,6 @@
     }
 }
 
-@ExperimentalCoroutinesApi
 @Composable
 private fun StatusMessage(
     viewModel: AlternateBouncerMessageAreaViewModel,
@@ -156,7 +153,6 @@
     }
 }
 
-@ExperimentalCoroutinesApi
 @Composable
 private fun DeviceEntryIcon(
     viewModel: AlternateBouncerUdfpsIconViewModel,
@@ -179,7 +175,6 @@
 }
 
 /** TODO (b/353955910): Validate accessibility CUJs */
-@ExperimentalCoroutinesApi
 @Composable
 private fun UdfpsA11yOverlay(
     viewModel: AlternateBouncerUdfpsAccessibilityOverlayViewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 478970f..d341702 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -45,7 +45,6 @@
 import com.android.systemui.keyguard.ui.composable.section.TopAreaSection
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import com.android.systemui.res.R
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
 import java.util.Optional
 import javax.inject.Inject
 import kotlin.math.roundToInt
@@ -130,9 +129,7 @@
                             if (!isShadeLayoutWide && !isBypassEnabled) {
                                 Box(modifier = Modifier.weight(weight = 1f)) {
                                     Column(Modifier.align(alignment = Alignment.TopStart)) {
-                                        if (PromotedNotificationUiAod.isEnabled) {
-                                            AodPromotedNotification()
-                                        }
+                                        AodPromotedNotificationArea()
                                         AodNotificationIcons(
                                             modifier = Modifier.padding(start = aodIconPadding)
                                         )
@@ -145,9 +142,7 @@
                                 }
                             } else {
                                 Column {
-                                    if (PromotedNotificationUiAod.isEnabled) {
-                                        AodPromotedNotification()
-                                    }
+                                    AodPromotedNotificationArea()
                                     AodNotificationIcons(
                                         modifier = Modifier.padding(start = aodIconPadding)
                                     )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index b66690c..abf7fdc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -54,6 +54,7 @@
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
 import com.android.systemui.statusbar.notification.promoted.AODPromotedNotification
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
 import com.android.systemui.statusbar.notification.promoted.ui.viewmodel.AODPromotedNotificationViewModel
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -110,8 +111,23 @@
     }
 
     @Composable
-    fun AodPromotedNotification() {
-        AODPromotedNotification(aodPromotedNotificationViewModelFactory)
+    fun AodPromotedNotificationArea(modifier: Modifier = Modifier) {
+        if (!PromotedNotificationUiAod.isEnabled) {
+            return
+        }
+
+        val isVisible by
+            keyguardRootViewModel.isAodPromotedNotifVisible.collectAsStateWithLifecycle()
+        val burnIn = rememberBurnIn(clockInteractor)
+
+        AnimatedVisibility(
+            visible = isVisible,
+            enter = fadeIn(),
+            exit = fadeOut(),
+            modifier = modifier.burnInAware(aodBurnInViewModel, burnIn.parameters),
+        ) {
+            AODPromotedNotification(aodPromotedNotificationViewModelFactory)
+        }
     }
 
     @Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
index 6738b97..1423d4a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
 package com.android.systemui.scene.ui.composable
 
 import androidx.compose.runtime.snapshotFlow
@@ -26,7 +24,6 @@
 import com.android.compose.animation.scene.observableTransitionState
 import com.android.systemui.scene.shared.model.SceneDataSource
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.flatMapLatest
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
index 51483a8..358d018 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
 package com.android.compose.nestedscroll
 
 import androidx.compose.foundation.gestures.FlingBehavior
@@ -28,7 +26,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.compose.test.runMonotonicClockTest
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.runTest
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
index 0245cf2..97fba3d 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
@@ -3,7 +3,6 @@
 import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.TestMonotonicFrameClock
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestCoroutineScheduler
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
@@ -18,7 +17,7 @@
  * Note: Please refer to the documentation for [runTest], as this feature utilizes it. This will
  * provide a comprehensive understanding of all its behaviors.
  */
-@OptIn(ExperimentalTestApi::class, ExperimentalCoroutinesApi::class)
+@OptIn(ExperimentalTestApi::class)
 fun runMonotonicClockTest(block: suspend MonotonicClockTestScope.() -> Unit) = runTest {
     val testScope: TestScope = this
 
diff --git a/packages/SystemUI/flag_check.py b/packages/SystemUI/flag_check.py
deleted file mode 100755
index d78ef5a..0000000
--- a/packages/SystemUI/flag_check.py
+++ /dev/null
@@ -1,138 +0,0 @@
-#! /usr/bin/env python3
-
-import sys
-import re
-import argparse
-
-# partially copied from tools/repohooks/rh/hooks.py
-
-TEST_MSG = """Commit message is missing a "Flag:" line.  It must match one of the
-following case-sensitive regex:
-
-    %s
-
-The Flag: stanza is regex matched and should describe whether your change is behind a flag or flags.
-As a CL author, you'll have a consistent place to describe the risk of the proposed change by explicitly calling out the name of the flag.
-For legacy flags use EXEMPT with your flag name.
-
-Some examples below:
-
-Flag: NONE Repohook Update
-Flag: TEST_ONLY
-Flag: EXEMPT resource only update
-Flag: EXEMPT bugfix
-Flag: EXEMPT refactor
-Flag: com.android.launcher3.enable_twoline_allapps
-Flag: com.google.android.apps.nexuslauncher.zero_state_web_data_loader
-
-Check the git history for more examples. It's a regex matched field. See go/android-flag-directive for more details on various formats.
-"""
-
-def main():
-    """Check the commit message for a 'Flag:' line."""
-    parser = argparse.ArgumentParser(
-        description='Check the commit message for a Flag: line.')
-    parser.add_argument('--msg',
-                        metavar='msg',
-                        type=str,
-                        nargs='?',
-                        default='HEAD',
-                        help='commit message to process.')
-    parser.add_argument(
-        '--files',
-        metavar='files',
-        nargs='?',
-        default='',
-        help=
-        'PREUPLOAD_FILES in repo upload to determine whether the check should run for the files.')
-    parser.add_argument(
-        '--project',
-        metavar='project',
-        type=str,
-        nargs='?',
-        default='',
-        help=
-        'REPO_PROJECT in repo upload to determine whether the check should run for this project.')
-
-    # Parse the arguments
-    args = parser.parse_args()
-    desc = args.msg
-    files = args.files
-    project = args.project
-
-    if not should_run_path(project, files):
-        return
-
-    field = 'Flag'
-    none = 'NONE'
-    testOnly = 'TEST_ONLY'
-    docsOnly = 'DOCS_ONLY'
-    exempt = 'EXEMPT'
-    justification = '<justification>'
-
-    # Aconfig Flag name format = <packageName>.<flagName>
-    # package name - Contains only lowercase alphabets + digits + '.' - Ex: com.android.launcher3
-    # For now alphabets, digits, "_", "." characters are allowed in flag name.
-    # Checks if there is "one dot" between packageName and flagName and not adding stricter format check
-    #common_typos_disable
-    flagName = '([a-zA-Z0-9.]+)([.]+)([a-zA-Z0-9_.]+)'
-
-    # None and Exempt needs justification
-    exemptRegex = fr'{exempt}\s*[a-zA-Z]+'
-    noneRegex = fr'{none}\s*[a-zA-Z]+'
-    #common_typos_enable
-
-    readableRegexMsg = '\n\tFlag: '+none+' '+justification+'\n\tFlag: <packageName>.<flagName>\n\tFlag: ' +exempt+' '+justification+'\n\tFlag: '+testOnly+'\n\tFlag: '+docsOnly
-
-    flagRegex = fr'^{field}: .*$'
-    check_flag = re.compile(flagRegex) #Flag:
-
-    # Ignore case for flag name format.
-    flagNameRegex = fr'(?i)^{field}:\s*({noneRegex}|{flagName}|{testOnly}|{docsOnly}|{exemptRegex})\s*'
-    check_flagName = re.compile(flagNameRegex) #Flag: <flag name format>
-
-    flagError = False
-    foundFlag = []
-    # Check for multiple "Flag:" lines and all lines should match this format
-    for line in desc.splitlines():
-        if check_flag.match(line):
-            if not check_flagName.match(line):
-                flagError = True
-                break
-            foundFlag.append(line)
-
-    # Throw error if
-    # 1. No "Flag:" line is found
-    # 2. "Flag:" doesn't follow right format.
-    if (not foundFlag) or (flagError):
-        error = TEST_MSG % (readableRegexMsg)
-        print(error)
-        sys.exit(1)
-
-    sys.exit(0)
-
-
-def should_run_path(project, files):
-    """Returns a boolean if this check should run with these paths.
-    If you want to check for a particular subdirectory under the path,
-    add a check here, call should_run_files and check for a specific sub dir path in should_run_files.
-    """
-    if not project:
-        return False
-    if project == 'platform/frameworks/base':
-        return should_run_files(files)
-    # Default case, run for all other projects which calls this script.
-    return True
-
-
-def should_run_files(files):
-    """Returns a boolean if this check should run with these files."""
-    if not files:
-        return False
-    if 'packages/SystemUI' in files:
-        return True
-    return False
-
-
-if __name__ == '__main__':
-    main()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
index 0d410cf..b6359c7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
@@ -116,6 +116,34 @@
         }
 
     @Test
+    fun isDreamButtonTooltipDismissedValue_byDefault_isFalse() =
+        testScope.runTest {
+            val isDreamButtonTooltipDismissed by
+                collectLastValue(underTest.isDreamButtonTooltipDismissed(MAIN_USER))
+            assertThat(isDreamButtonTooltipDismissed).isFalse()
+        }
+
+    @Test
+    fun isDreamButtonTooltipDismissedValue_onSet_isTrue() =
+        testScope.runTest {
+            val isDreamButtonTooltipDismissed by
+                collectLastValue(underTest.isDreamButtonTooltipDismissed(MAIN_USER))
+
+            underTest.setDreamButtonTooltipDismissed(MAIN_USER)
+            assertThat(isDreamButtonTooltipDismissed).isTrue()
+        }
+
+    @Test
+    fun isDreamButtonTooltipDismissedValue_onSetForDifferentUser_isStillFalse() =
+        testScope.runTest {
+            val isDreamButtonTooltipDismissed by
+                collectLastValue(underTest.isDreamButtonTooltipDismissed(MAIN_USER))
+
+            underTest.setDreamButtonTooltipDismissed(SECONDARY_USER)
+            assertThat(isDreamButtonTooltipDismissed).isFalse()
+        }
+
+    @Test
     fun getSharedPreferences_whenFileRestored() =
         testScope.runTest {
             val isCtaDismissed by collectLastValue(underTest.isCtaDismissed(MAIN_USER))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
index 809099e..eb1f1d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
@@ -38,19 +38,19 @@
 import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_BACKGROUND_SETTING
 import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
-import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.communal.shared.model.WhenToDream
 import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
 import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.collectLastValue
 import com.android.systemui.kosmos.runTest
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
-import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -62,9 +62,11 @@
 @RunWith(ParameterizedAndroidJunit4::class)
 class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiTestCase() {
     private val kosmos =
-        testKosmos().apply { mainResources = mContext.orCreateTestableResources.resources }
-    private val testScope = kosmos.testScope
-    private lateinit var underTest: CommunalSettingsRepository
+        testKosmos()
+            .apply { mainResources = mContext.orCreateTestableResources.resources }
+            .useUnconfinedTestDispatcher()
+
+    private val Kosmos.underTest by Kosmos.Fixture { communalSettingsRepository }
 
     init {
         mSetFlagsRule.setFlagsParameterization(flags!!)
@@ -76,98 +78,105 @@
         setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_FEATURES_NONE)
         setKeyguardFeaturesDisabled(SECONDARY_USER, KEYGUARD_DISABLE_FEATURES_NONE)
         setKeyguardFeaturesDisabled(WORK_PROFILE, KEYGUARD_DISABLE_FEATURES_NONE)
-        underTest = kosmos.communalSettingsRepository
     }
 
     @EnableFlags(FLAG_COMMUNAL_HUB)
     @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     @Test
-    fun getFlagEnabled_bothEnabled() {
-        kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+    fun getFlagEnabled_bothEnabled() =
+        kosmos.runTest {
+            fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
 
-        assertThat(underTest.getFlagEnabled()).isTrue()
-    }
+            assertThat(underTest.getFlagEnabled()).isTrue()
+        }
 
     @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
     @Test
-    fun getFlagEnabled_bothDisabled() {
-        kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
+    fun getFlagEnabled_bothDisabled() =
+        kosmos.runTest {
+            fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
 
-        assertThat(underTest.getFlagEnabled()).isFalse()
-    }
+            assertThat(underTest.getFlagEnabled()).isFalse()
+        }
 
     @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
     @Test
-    fun getFlagEnabled_onlyClassicFlagEnabled() {
-        kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+    fun getFlagEnabled_onlyClassicFlagEnabled() =
+        kosmos.runTest {
+            fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
 
-        assertThat(underTest.getFlagEnabled()).isFalse()
-    }
+            assertThat(underTest.getFlagEnabled()).isFalse()
+        }
 
     @EnableFlags(FLAG_COMMUNAL_HUB)
     @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     @Test
-    fun getFlagEnabled_onlyTrunkFlagEnabled() {
-        kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
+    fun getFlagEnabled_onlyTrunkFlagEnabled() =
+        kosmos.runTest {
+            fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
 
-        assertThat(underTest.getFlagEnabled()).isFalse()
-    }
+            assertThat(underTest.getFlagEnabled()).isFalse()
+        }
 
     @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
     @DisableFlags(FLAG_COMMUNAL_HUB)
     @Test
-    fun getFlagEnabled_mobileConfigEnabled() {
-        mContext.orCreateTestableResources.addOverride(
-            com.android.internal.R.bool.config_glanceableHubEnabled,
-            true,
-        )
+    fun getFlagEnabled_mobileConfigEnabled() =
+        kosmos.runTest {
+            mContext.orCreateTestableResources.addOverride(
+                com.android.internal.R.bool.config_glanceableHubEnabled,
+                true,
+            )
 
-        assertThat(underTest.getFlagEnabled()).isTrue()
-    }
+            assertThat(underTest.getFlagEnabled()).isTrue()
+        }
 
     @DisableFlags(FLAG_GLANCEABLE_HUB_V2, FLAG_COMMUNAL_HUB)
     @Test
-    fun getFlagEnabled_onlyMobileConfigEnabled() {
-        mContext.orCreateTestableResources.addOverride(
-            com.android.internal.R.bool.config_glanceableHubEnabled,
-            true,
-        )
+    fun getFlagEnabled_onlyMobileConfigEnabled() =
+        kosmos.runTest {
+            mContext.orCreateTestableResources.addOverride(
+                com.android.internal.R.bool.config_glanceableHubEnabled,
+                true,
+            )
 
-        assertThat(underTest.getFlagEnabled()).isFalse()
-    }
+            assertThat(underTest.getFlagEnabled()).isFalse()
+        }
 
     @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
     @DisableFlags(FLAG_COMMUNAL_HUB)
     @Test
-    fun getFlagEnabled_onlyMobileFlagEnabled() {
-        mContext.orCreateTestableResources.addOverride(
-            com.android.internal.R.bool.config_glanceableHubEnabled,
-            false,
-        )
+    fun getFlagEnabled_onlyMobileFlagEnabled() =
+        kosmos.runTest {
+            mContext.orCreateTestableResources.addOverride(
+                com.android.internal.R.bool.config_glanceableHubEnabled,
+                false,
+            )
 
-        assertThat(underTest.getFlagEnabled()).isFalse()
-    }
+            assertThat(underTest.getFlagEnabled()).isFalse()
+        }
 
     @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
     @DisableFlags(FLAG_COMMUNAL_HUB)
     @Test
-    fun getFlagEnabled_oldFlagIgnored() {
-        // New config flag enabled.
-        mContext.orCreateTestableResources.addOverride(
-            com.android.internal.R.bool.config_glanceableHubEnabled,
-            true,
-        )
+    fun getFlagEnabled_oldFlagIgnored() =
+        kosmos.runTest {
+            // New config flag enabled.
+            mContext.orCreateTestableResources.addOverride(
+                com.android.internal.R.bool.config_glanceableHubEnabled,
+                true,
+            )
 
-        // Old config flag disabled.
-        kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
+            // Old config flag disabled.
+            fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
 
-        assertThat(underTest.getFlagEnabled()).isTrue()
-    }
+            assertThat(underTest.getFlagEnabled()).isTrue()
+        }
 
     @EnableFlags(FLAG_COMMUNAL_HUB)
     @Test
     fun secondaryUserIsInvalid() =
-        testScope.runTest {
+        kosmos.runTest {
             val enabledState by collectLastValue(underTest.getEnabledState(SECONDARY_USER))
 
             assertThat(enabledState?.enabled).isFalse()
@@ -187,7 +196,7 @@
     @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun communalHubFlagIsDisabled() =
-        testScope.runTest {
+        kosmos.runTest {
             val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
             assertThat(enabledState?.enabled).isFalse()
             assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG)
@@ -196,35 +205,23 @@
     @EnableFlags(FLAG_COMMUNAL_HUB)
     @Test
     fun hubIsDisabledByUser() =
-        testScope.runTest {
-            kosmos.fakeSettings.putIntForUser(
-                Settings.Secure.GLANCEABLE_HUB_ENABLED,
-                0,
-                PRIMARY_USER.id,
-            )
+        kosmos.runTest {
+            fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id)
             val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
             assertThat(enabledState?.enabled).isFalse()
             assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_USER_SETTING)
 
-            kosmos.fakeSettings.putIntForUser(
-                Settings.Secure.GLANCEABLE_HUB_ENABLED,
-                1,
-                SECONDARY_USER.id,
-            )
+            fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 1, SECONDARY_USER.id)
             assertThat(enabledState?.enabled).isFalse()
 
-            kosmos.fakeSettings.putIntForUser(
-                Settings.Secure.GLANCEABLE_HUB_ENABLED,
-                1,
-                PRIMARY_USER.id,
-            )
+            fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 1, PRIMARY_USER.id)
             assertThat(enabledState?.enabled).isTrue()
         }
 
     @EnableFlags(FLAG_COMMUNAL_HUB)
     @Test
     fun hubIsDisabledByDevicePolicy() =
-        testScope.runTest {
+        kosmos.runTest {
             val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
             assertThat(enabledState?.enabled).isTrue()
 
@@ -236,7 +233,7 @@
     @EnableFlags(FLAG_COMMUNAL_HUB)
     @Test
     fun widgetsAllowedForWorkProfile_isFalse_whenDisallowedByDevicePolicy() =
-        testScope.runTest {
+        kosmos.runTest {
             val widgetsAllowedForWorkProfile by
                 collectLastValue(underTest.getAllowedByDevicePolicy(WORK_PROFILE))
             assertThat(widgetsAllowedForWorkProfile).isTrue()
@@ -248,7 +245,7 @@
     @EnableFlags(FLAG_COMMUNAL_HUB)
     @Test
     fun hubIsEnabled_whenDisallowedByDevicePolicyForWorkProfile() =
-        testScope.runTest {
+        kosmos.runTest {
             val enabledStateForPrimaryUser by
                 collectLastValue(underTest.getEnabledState(PRIMARY_USER))
             assertThat(enabledStateForPrimaryUser?.enabled).isTrue()
@@ -260,15 +257,11 @@
     @EnableFlags(FLAG_COMMUNAL_HUB)
     @Test
     fun hubIsDisabledByUserAndDevicePolicy() =
-        testScope.runTest {
+        kosmos.runTest {
             val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
             assertThat(enabledState?.enabled).isTrue()
 
-            kosmos.fakeSettings.putIntForUser(
-                Settings.Secure.GLANCEABLE_HUB_ENABLED,
-                0,
-                PRIMARY_USER.id,
-            )
+            fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id)
             setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_WIDGETS_ALL)
 
             assertThat(enabledState?.enabled).isFalse()
@@ -282,17 +275,17 @@
     @Test
     @DisableFlags(FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND)
     fun backgroundType_defaultValue() =
-        testScope.runTest {
+        kosmos.runTest {
             val backgroundType by collectLastValue(underTest.getBackground(PRIMARY_USER))
             assertThat(backgroundType).isEqualTo(CommunalBackgroundType.ANIMATED)
         }
 
     @Test
     fun backgroundType_verifyAllValues() =
-        testScope.runTest {
+        kosmos.runTest {
             val backgroundType by collectLastValue(underTest.getBackground(PRIMARY_USER))
             for (type in CommunalBackgroundType.entries) {
-                kosmos.fakeSettings.putIntForUser(
+                fakeSettings.putIntForUser(
                     GLANCEABLE_HUB_BACKGROUND_SETTING,
                     type.value,
                     PRIMARY_USER.id,
@@ -308,30 +301,71 @@
 
     @Test
     fun screensaverDisabledByUser() =
-        testScope.runTest {
+        kosmos.runTest {
             val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER))
 
-            kosmos.fakeSettings.putIntForUser(
-                Settings.Secure.SCREENSAVER_ENABLED,
-                0,
-                PRIMARY_USER.id,
-            )
+            fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ENABLED, 0, PRIMARY_USER.id)
 
             assertThat(enabledState).isFalse()
         }
 
     @Test
     fun screensaverEnabledByUser() =
-        testScope.runTest {
+        kosmos.runTest {
             val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER))
 
-            kosmos.fakeSettings.putIntForUser(
-                Settings.Secure.SCREENSAVER_ENABLED,
+            fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ENABLED, 1, PRIMARY_USER.id)
+
+            assertThat(enabledState).isTrue()
+        }
+
+    @Test
+    fun whenToDream_charging() =
+        kosmos.runTest {
+            val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
+
+            fakeSettings.putIntForUser(
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
                 1,
                 PRIMARY_USER.id,
             )
 
-            assertThat(enabledState).isTrue()
+            assertThat(whenToDreamState).isEqualTo(WhenToDream.WHILE_CHARGING)
+        }
+
+    @Test
+    fun whenToDream_docked() =
+        kosmos.runTest {
+            val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
+
+            fakeSettings.putIntForUser(
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+                1,
+                PRIMARY_USER.id,
+            )
+
+            assertThat(whenToDreamState).isEqualTo(WhenToDream.WHILE_DOCKED)
+        }
+
+    @Test
+    fun whenToDream_postured() =
+        kosmos.runTest {
+            val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
+
+            fakeSettings.putIntForUser(
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
+                1,
+                PRIMARY_USER.id,
+            )
+
+            assertThat(whenToDreamState).isEqualTo(WhenToDream.WHILE_POSTURED)
+        }
+
+    @Test
+    fun whenToDream_default() =
+        kosmos.runTest {
+            val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
+            assertThat(whenToDreamState).isEqualTo(WhenToDream.NEVER)
         }
 
     private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
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 7ae0577..c9e7a5d 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
@@ -38,13 +38,9 @@
 import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.common.data.repository.batteryRepository
+import com.android.systemui.common.data.repository.fake
 import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
-import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
-import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
-import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
-import com.android.systemui.communal.data.repository.FakeCommunalSmartspaceRepository
-import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
-import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
 import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
 import com.android.systemui.communal.data.repository.fakeCommunalPrefsRepository
 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
@@ -53,52 +49,49 @@
 import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
+import com.android.systemui.communal.posturing.data.repository.fake
+import com.android.systemui.communal.posturing.data.repository.posturingRepository
+import com.android.systemui.communal.posturing.shared.model.PosturedState
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.EditModeState
-import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
-import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dock.DockManager
+import com.android.systemui.dock.fakeDockManager
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.plugins.activityStarter
-import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.settings.fakeUserTracker
 import com.android.systemui.statusbar.phone.fakeManagedProfileController
 import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.utils.leaks.FakeManagedProfileController
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.advanceTimeBy
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mock
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
 import platform.test.runner.parameterized.Parameters
 
@@ -107,32 +100,15 @@
  * [CommunalInteractorCommunalDisabledTest].
  */
 @SmallTest
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(ParameterizedAndroidJunit4::class)
 class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
-    @Mock private lateinit var mainUser: UserInfo
-    @Mock private lateinit var secondaryUser: UserInfo
+    private val mainUser =
+        UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
+    private val secondaryUser = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
 
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
 
-    private lateinit var tutorialRepository: FakeCommunalTutorialRepository
-    private lateinit var communalRepository: FakeCommunalSceneRepository
-    private lateinit var mediaRepository: FakeCommunalMediaRepository
-    private lateinit var widgetRepository: FakeCommunalWidgetRepository
-    private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository
-    private lateinit var userRepository: FakeUserRepository
-    private lateinit var keyguardRepository: FakeKeyguardRepository
-    private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
-    private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter
-    private lateinit var sceneInteractor: SceneInteractor
-    private lateinit var communalSceneInteractor: CommunalSceneInteractor
-    private lateinit var userTracker: FakeUserTracker
-    private lateinit var activityStarter: ActivityStarter
-    private lateinit var userManager: UserManager
-    private lateinit var managedProfileController: FakeManagedProfileController
-
-    private lateinit var underTest: CommunalInteractor
+    private val Kosmos.underTest by Kosmos.Fixture { communalInteractor }
 
     init {
         mSetFlagsRule.setFlagsParameterization(flags)
@@ -140,128 +116,104 @@
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        tutorialRepository = kosmos.fakeCommunalTutorialRepository
-        communalRepository = kosmos.fakeCommunalSceneRepository
-        mediaRepository = kosmos.fakeCommunalMediaRepository
-        widgetRepository = kosmos.fakeCommunalWidgetRepository
-        smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository
-        userRepository = kosmos.fakeUserRepository
-        keyguardRepository = kosmos.fakeKeyguardRepository
-        editWidgetsActivityStarter = kosmos.editWidgetsActivityStarter
-        communalPrefsRepository = kosmos.fakeCommunalPrefsRepository
-        sceneInteractor = kosmos.sceneInteractor
-        communalSceneInteractor = kosmos.communalSceneInteractor
-        userTracker = kosmos.fakeUserTracker
-        activityStarter = kosmos.activityStarter
-        userManager = kosmos.userManager
-        managedProfileController = kosmos.fakeManagedProfileController
-
-        whenever(mainUser.isMain).thenReturn(true)
-        whenever(secondaryUser.isMain).thenReturn(false)
-        whenever(userManager.isQuietModeEnabled(any<UserHandle>())).thenReturn(false)
-        whenever(userManager.isManagedProfile(anyInt())).thenReturn(false)
-        userRepository.setUserInfos(listOf(mainUser, secondaryUser))
+        whenever(kosmos.userManager.isQuietModeEnabled(any<UserHandle>())).thenReturn(false)
+        whenever(kosmos.userManager.isManagedProfile(anyInt())).thenReturn(false)
+        kosmos.fakeUserRepository.setUserInfos(listOf(mainUser, secondaryUser))
 
         kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
         mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
-
-        underTest = kosmos.communalInteractor
     }
 
     @Test
     fun communalEnabled_true() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(mainUser)
-            runCurrent()
+        kosmos.runTest {
+            fakeUserRepository.setSelectedUserInfo(mainUser)
             assertThat(underTest.isCommunalEnabled.value).isTrue()
         }
 
     @Test
     fun isCommunalAvailable_storageUnlockedAndMainUser_true() =
-        testScope.runTest {
+        kosmos.runTest {
             val isAvailable by collectLastValue(underTest.isCommunalAvailable)
             assertThat(isAvailable).isFalse()
 
-            keyguardRepository.setIsEncryptedOrLockdown(false)
-            userRepository.setSelectedUserInfo(mainUser)
-            keyguardRepository.setKeyguardShowing(true)
+            fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+            fakeUserRepository.setSelectedUserInfo(mainUser)
+            fakeKeyguardRepository.setKeyguardShowing(true)
 
             assertThat(isAvailable).isTrue()
         }
 
     @Test
     fun isCommunalAvailable_storageLockedAndMainUser_false() =
-        testScope.runTest {
+        kosmos.runTest {
             val isAvailable by collectLastValue(underTest.isCommunalAvailable)
             assertThat(isAvailable).isFalse()
 
-            keyguardRepository.setIsEncryptedOrLockdown(true)
-            userRepository.setSelectedUserInfo(mainUser)
-            keyguardRepository.setKeyguardShowing(true)
+            fakeKeyguardRepository.setIsEncryptedOrLockdown(true)
+            fakeUserRepository.setSelectedUserInfo(mainUser)
+            fakeKeyguardRepository.setKeyguardShowing(true)
 
             assertThat(isAvailable).isFalse()
         }
 
     @Test
     fun isCommunalAvailable_storageUnlockedAndSecondaryUser_false() =
-        testScope.runTest {
+        kosmos.runTest {
             val isAvailable by collectLastValue(underTest.isCommunalAvailable)
             assertThat(isAvailable).isFalse()
 
-            keyguardRepository.setIsEncryptedOrLockdown(false)
-            userRepository.setSelectedUserInfo(secondaryUser)
-            keyguardRepository.setKeyguardShowing(true)
+            fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+            fakeUserRepository.setSelectedUserInfo(secondaryUser)
+            fakeKeyguardRepository.setKeyguardShowing(true)
 
             assertThat(isAvailable).isFalse()
         }
 
     @Test
     fun isCommunalAvailable_whenKeyguardShowing_true() =
-        testScope.runTest {
+        kosmos.runTest {
             val isAvailable by collectLastValue(underTest.isCommunalAvailable)
             assertThat(isAvailable).isFalse()
 
-            keyguardRepository.setIsEncryptedOrLockdown(false)
-            userRepository.setSelectedUserInfo(mainUser)
-            keyguardRepository.setKeyguardShowing(true)
+            fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+            fakeUserRepository.setSelectedUserInfo(mainUser)
+            fakeKeyguardRepository.setKeyguardShowing(true)
 
             assertThat(isAvailable).isTrue()
         }
 
     @Test
     fun isCommunalAvailable_communalDisabled_false() =
-        testScope.runTest {
+        kosmos.runTest {
             mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
 
             val isAvailable by collectLastValue(underTest.isCommunalAvailable)
             assertThat(isAvailable).isFalse()
 
-            keyguardRepository.setIsEncryptedOrLockdown(false)
-            userRepository.setSelectedUserInfo(mainUser)
-            keyguardRepository.setKeyguardShowing(true)
+            fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+            fakeUserRepository.setSelectedUserInfo(mainUser)
+            fakeKeyguardRepository.setKeyguardShowing(true)
 
             assertThat(isAvailable).isFalse()
         }
 
     @Test
     fun widget_tutorialCompletedAndWidgetsAvailable_showWidgetContent() =
-        testScope.runTest {
+        kosmos.runTest {
             // Keyguard showing, and tutorial completed.
-            keyguardRepository.setKeyguardShowing(true)
-            keyguardRepository.setKeyguardOccluded(false)
-            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+            fakeKeyguardRepository.setKeyguardShowing(true)
+            fakeKeyguardRepository.setKeyguardOccluded(false)
+            fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
             val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
-            userRepository.setUserInfos(userInfos)
-            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
-            runCurrent()
+            fakeUserRepository.setUserInfos(userInfos)
+            fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
 
             // Widgets available.
-            widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
-            widgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id)
-            widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
+            fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+            fakeCommunalWidgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id)
+            fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
 
             val widgetContent by collectLastValue(underTest.widgetContent)
 
@@ -356,18 +308,18 @@
         totalTargets: Int,
         expectedSizes: List<CommunalContentSize>,
     ) =
-        testScope.runTest {
+        kosmos.runTest {
             // Keyguard showing, and tutorial completed.
-            keyguardRepository.setKeyguardShowing(true)
-            keyguardRepository.setKeyguardOccluded(false)
-            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+            fakeKeyguardRepository.setKeyguardShowing(true)
+            fakeKeyguardRepository.setKeyguardOccluded(false)
+            fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
             val targets = mutableListOf<CommunalSmartspaceTimer>()
             for (index in 0 until totalTargets) {
                 targets.add(smartspaceTimer(index.toString()))
             }
 
-            smartspaceRepository.setTimers(targets)
+            fakeCommunalSmartspaceRepository.setTimers(targets)
 
             val smartspaceContent by collectLastValue(underTest.ongoingContent(false))
             assertThat(smartspaceContent?.size).isEqualTo(totalTargets)
@@ -378,12 +330,12 @@
 
     @Test
     fun umo_mediaPlaying_showsUmo() =
-        testScope.runTest {
+        kosmos.runTest {
             // Tutorial completed.
-            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+            fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
             // Media is playing.
-            mediaRepository.mediaActive()
+            fakeCommunalMediaRepository.mediaActive()
 
             val umoContent by collectLastValue(underTest.ongoingContent(true))
 
@@ -394,12 +346,12 @@
 
     @Test
     fun umo_mediaPlaying_mediaHostNotVisible_hidesUmo() =
-        testScope.runTest {
+        kosmos.runTest {
             // Tutorial completed.
-            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+            fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
             // Media is playing.
-            mediaRepository.mediaActive()
+            fakeCommunalMediaRepository.mediaActive()
 
             val umoContent by collectLastValue(underTest.ongoingContent(false))
             assertThat(umoContent?.size).isEqualTo(0)
@@ -409,26 +361,26 @@
     @Test
     @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
     fun ongoing_shouldOrderAndSizeByTimestamp() =
-        testScope.runTest {
+        kosmos.runTest {
             // Keyguard showing, and tutorial completed.
-            keyguardRepository.setKeyguardShowing(true)
-            keyguardRepository.setKeyguardOccluded(false)
-            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+            fakeKeyguardRepository.setKeyguardShowing(true)
+            fakeKeyguardRepository.setKeyguardOccluded(false)
+            fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
             // Timer1 started
             val timer1 = smartspaceTimer("timer1", timestamp = 1L)
-            smartspaceRepository.setTimers(listOf(timer1))
+            fakeCommunalSmartspaceRepository.setTimers(listOf(timer1))
 
             // Umo started
-            mediaRepository.mediaActive(timestamp = 2L)
+            fakeCommunalMediaRepository.mediaActive(timestamp = 2L)
 
             // Timer2 started
             val timer2 = smartspaceTimer("timer2", timestamp = 3L)
-            smartspaceRepository.setTimers(listOf(timer1, timer2))
+            fakeCommunalSmartspaceRepository.setTimers(listOf(timer1, timer2))
 
             // Timer3 started
             val timer3 = smartspaceTimer("timer3", timestamp = 4L)
-            smartspaceRepository.setTimers(listOf(timer1, timer2, timer3))
+            fakeCommunalSmartspaceRepository.setTimers(listOf(timer1, timer2, timer3))
 
             val ongoingContent by collectLastValue(underTest.ongoingContent(true))
             assertThat(ongoingContent?.size).isEqualTo(4)
@@ -447,8 +399,8 @@
 
     @Test
     fun ctaTile_showsByDefault() =
-        testScope.runTest {
-            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+        kosmos.runTest {
+            fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
             val ctaTileContent by collectLastValue(underTest.ctaTileContent)
 
@@ -461,14 +413,13 @@
 
     @Test
     fun ctaTile_afterDismiss_doesNotShow() =
-        testScope.runTest {
+        kosmos.runTest {
             // Set to main user, so we can dismiss the tile for the main user.
-            val user = userRepository.asMainUser()
-            userTracker.set(userInfos = listOf(user), selectedUserIndex = 0)
-            runCurrent()
+            val user = fakeUserRepository.asMainUser()
+            fakeUserTracker.set(userInfos = listOf(user), selectedUserIndex = 0)
 
-            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
-            communalPrefsRepository.setCtaDismissed(user)
+            fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+            fakeCommunalPrefsRepository.setCtaDismissed(user)
 
             val ctaTileContent by collectLastValue(underTest.ctaTileContent)
 
@@ -477,36 +428,30 @@
 
     @Test
     fun listensToSceneChange() =
-        testScope.runTest {
+        kosmos.runTest {
             kosmos.setCommunalAvailable(true)
-            runCurrent()
 
-            var desiredScene = collectLastValue(underTest.desiredScene)
-            runCurrent()
-            assertThat(desiredScene()).isEqualTo(CommunalScenes.Blank)
+            val desiredScene by collectLastValue(underTest.desiredScene)
+            assertThat(desiredScene).isEqualTo(CommunalScenes.Blank)
 
             val targetScene = CommunalScenes.Communal
-            communalRepository.changeScene(targetScene)
-            desiredScene = collectLastValue(underTest.desiredScene)
-            runCurrent()
-            assertThat(desiredScene()).isEqualTo(targetScene)
+            fakeCommunalSceneRepository.changeScene(targetScene)
+            assertThat(desiredScene).isEqualTo(targetScene)
         }
 
     @Test
     fun updatesScene() =
-        testScope.runTest {
+        kosmos.runTest {
             val targetScene = CommunalScenes.Communal
-
             underTest.changeScene(targetScene, "test")
 
-            val desiredScene = collectLastValue(communalRepository.currentScene)
-            runCurrent()
-            assertThat(desiredScene()).isEqualTo(targetScene)
+            val desiredScene by collectLastValue(fakeCommunalSceneRepository.currentScene)
+            assertThat(desiredScene).isEqualTo(targetScene)
         }
 
     @Test
     fun transitionProgress_onTargetScene_fullProgress() =
-        testScope.runTest {
+        kosmos.runTest {
             val targetScene = CommunalScenes.Blank
             val transitionProgressFlow = underTest.transitionProgressToScene(targetScene)
             val transitionProgress by collectLastValue(transitionProgressFlow)
@@ -524,7 +469,7 @@
 
     @Test
     fun transitionProgress_notOnTargetScene_noProgress() =
-        testScope.runTest {
+        kosmos.runTest {
             val targetScene = CommunalScenes.Blank
             val currentScene = CommunalScenes.Communal
             val transitionProgressFlow = underTest.transitionProgressToScene(targetScene)
@@ -543,7 +488,7 @@
 
     @Test
     fun transitionProgress_transitioningToTrackedScene() =
-        testScope.runTest {
+        kosmos.runTest {
             val currentScene = CommunalScenes.Communal
             val targetScene = CommunalScenes.Blank
             val transitionProgressFlow = underTest.transitionProgressToScene(targetScene)
@@ -591,7 +536,7 @@
 
     @Test
     fun transitionProgress_transitioningAwayFromTrackedScene() =
-        testScope.runTest {
+        kosmos.runTest {
             val currentScene = CommunalScenes.Blank
             val targetScene = CommunalScenes.Communal
             val transitionProgressFlow = underTest.transitionProgressToScene(currentScene)
@@ -642,52 +587,42 @@
 
     @Test
     fun isCommunalShowing() =
-        testScope.runTest {
+        kosmos.runTest {
             kosmos.setCommunalAvailable(true)
-            runCurrent()
 
-            var isCommunalShowing = collectLastValue(underTest.isCommunalShowing)
-            runCurrent()
-            assertThat(isCommunalShowing()).isEqualTo(false)
+            val isCommunalShowing by collectLastValue(underTest.isCommunalShowing)
+            assertThat(isCommunalShowing).isEqualTo(false)
 
             underTest.changeScene(CommunalScenes.Communal, "test")
-
-            isCommunalShowing = collectLastValue(underTest.isCommunalShowing)
-            runCurrent()
-            assertThat(isCommunalShowing()).isEqualTo(true)
+            assertThat(isCommunalShowing).isEqualTo(true)
         }
 
     @Test
     fun isCommunalShowing_whenSceneContainerDisabled() =
-        testScope.runTest {
+        kosmos.runTest {
             kosmos.setCommunalAvailable(true)
-            runCurrent()
 
             // Verify default is false
             val isCommunalShowing by collectLastValue(underTest.isCommunalShowing)
-            runCurrent()
             assertThat(isCommunalShowing).isFalse()
 
             // Verify scene changes with the flag doesn't have any impact
             sceneInteractor.changeScene(Scenes.Communal, loggingReason = "")
-            runCurrent()
             assertThat(isCommunalShowing).isFalse()
 
             // Verify scene changes (without the flag) to communal sets the value to true
             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, "test")
-            runCurrent()
             assertThat(isCommunalShowing).isFalse()
         }
 
     @Test
     @EnableSceneContainer
     fun isCommunalShowing_whenSceneContainerEnabled() =
-        testScope.runTest {
+        kosmos.runTest {
             // Verify default is false
             val isCommunalShowing by collectLastValue(underTest.isCommunalShowing)
             assertThat(isCommunalShowing).isFalse()
@@ -704,7 +639,7 @@
     @Test
     @EnableSceneContainer
     fun isCommunalShowing_whenSceneContainerEnabledAndChangeToLegacyScene() =
-        testScope.runTest {
+        kosmos.runTest {
             // Verify default is false
             val isCommunalShowing by collectLastValue(underTest.isCommunalShowing)
             assertThat(isCommunalShowing).isFalse()
@@ -720,21 +655,19 @@
 
     @Test
     fun isIdleOnCommunal() =
-        testScope.runTest {
+        kosmos.runTest {
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Idle(CommunalScenes.Blank)
                 )
-            communalRepository.setTransitionState(transitionState)
+            fakeCommunalSceneRepository.setTransitionState(transitionState)
 
             // isIdleOnCommunal is false when not on communal.
             val isIdleOnCommunal by collectLastValue(underTest.isIdleOnCommunal)
-            runCurrent()
             assertThat(isIdleOnCommunal).isEqualTo(false)
 
             // Transition to communal.
             transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Communal)
-            runCurrent()
 
             // isIdleOnCommunal is now true since we're on communal.
             assertThat(isIdleOnCommunal).isEqualTo(true)
@@ -749,7 +682,6 @@
                     isInitiatedByUserInput = false,
                     isUserInputOngoing = flowOf(false),
                 )
-            runCurrent()
 
             // isIdleOnCommunal turns false as soon as transition away starts.
             assertThat(isIdleOnCommunal).isEqualTo(false)
@@ -757,12 +689,12 @@
 
     @Test
     fun isCommunalVisible() =
-        testScope.runTest {
+        kosmos.runTest {
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Idle(CommunalScenes.Blank)
                 )
-            communalRepository.setTransitionState(transitionState)
+            fakeCommunalSceneRepository.setTransitionState(transitionState)
 
             // isCommunalVisible is false when not on communal.
             val isCommunalVisible by collectLastValue(underTest.isCommunalVisible)
@@ -805,7 +737,7 @@
 
     @Test
     fun testShowWidgetEditorStartsActivity() =
-        testScope.runTest {
+        kosmos.runTest {
             val editModeState by collectLastValue(communalSceneInteractor.editModeState)
 
             underTest.showWidgetEditor()
@@ -816,14 +748,14 @@
 
     @Test
     fun showWidgetEditor_openWidgetPickerOnStart_startsActivity() =
-        testScope.runTest {
+        kosmos.runTest {
             underTest.showWidgetEditor(shouldOpenWidgetPickerOnStart = true)
             verify(editWidgetsActivityStarter).startActivity(shouldOpenWidgetPickerOnStart = true)
         }
 
     @Test
     fun navigateToCommunalWidgetSettings_startsActivity() =
-        testScope.runTest {
+        kosmos.runTest {
             underTest.navigateToCommunalWidgetSettings()
             val intentCaptor = argumentCaptor<Intent>()
             verify(activityStarter)
@@ -833,23 +765,22 @@
 
     @Test
     fun filterWidgets_whenUserProfileRemoved() =
-        testScope.runTest {
+        kosmos.runTest {
             // Keyguard showing, and tutorial completed.
-            keyguardRepository.setKeyguardShowing(true)
-            keyguardRepository.setKeyguardOccluded(false)
-            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+            fakeKeyguardRepository.setKeyguardShowing(true)
+            fakeKeyguardRepository.setKeyguardOccluded(false)
+            fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
             // Only main user exists.
             val userInfos = listOf(MAIN_USER_INFO)
-            userRepository.setUserInfos(userInfos)
-            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
-            runCurrent()
+            fakeUserRepository.setUserInfos(userInfos)
+            fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
 
             val widgetContent by collectLastValue(underTest.widgetContent)
             // Given three widgets, and one of them is associated with pre-existing work profile.
-            widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
-            widgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id)
-            widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
+            fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+            fakeCommunalWidgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id)
+            fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
 
             // One widget is filtered out and the remaining two link to main user id.
             assertThat(checkNotNull(widgetContent).size).isEqualTo(2)
@@ -867,17 +798,16 @@
 
     @Test
     fun widgetContent_inQuietMode() =
-        testScope.runTest {
+        kosmos.runTest {
             // Keyguard showing, and tutorial completed.
-            keyguardRepository.setKeyguardShowing(true)
-            keyguardRepository.setKeyguardOccluded(false)
-            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+            fakeKeyguardRepository.setKeyguardShowing(true)
+            fakeKeyguardRepository.setKeyguardOccluded(false)
+            fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
             // Work profile is set up.
             val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
-            userRepository.setUserInfos(userInfos)
-            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
-            runCurrent()
+            fakeUserRepository.setUserInfos(userInfos)
+            fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
 
             // When work profile is paused.
             whenever(userManager.isQuietModeEnabled(eq(UserHandle.of(USER_INFO_WORK.id))))
@@ -885,9 +815,9 @@
             whenever(userManager.isManagedProfile(eq(USER_INFO_WORK.id))).thenReturn(true)
 
             val widgetContent by collectLastValue(underTest.widgetContent)
-            widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
-            widgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id)
-            widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
+            fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+            fakeCommunalWidgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id)
+            fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
 
             // The work profile widget is in quiet mode, while other widgets are not.
             assertThat(widgetContent).hasSize(3)
@@ -911,23 +841,25 @@
 
     @Test
     fun filterWidgets_whenDisallowedByDevicePolicyForWorkProfile() =
-        testScope.runTest {
+        kosmos.runTest {
             // Keyguard showing, and tutorial completed.
-            keyguardRepository.setKeyguardShowing(true)
-            keyguardRepository.setKeyguardOccluded(false)
-            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+            fakeKeyguardRepository.setKeyguardShowing(true)
+            fakeKeyguardRepository.setKeyguardOccluded(false)
+            fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
             val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
-            userRepository.setUserInfos(userInfos)
-            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
-            userRepository.setSelectedUserInfo(MAIN_USER_INFO)
-            runCurrent()
+            fakeUserRepository.setUserInfos(userInfos)
+            fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
+            fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
 
             val widgetContent by collectLastValue(underTest.widgetContent)
             // One available work widget, one pending work widget, and one regular available widget.
-            widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
-            widgetRepository.addPendingWidget(appWidgetId = 2, userId = USER_INFO_WORK.id)
-            widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
+            fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+            fakeCommunalWidgetRepository.addPendingWidget(
+                appWidgetId = 2,
+                userId = USER_INFO_WORK.id,
+            )
+            fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
 
             setKeyguardFeaturesDisabled(
                 USER_INFO_WORK,
@@ -941,23 +873,25 @@
 
     @Test
     fun filterWidgets_whenAllowedByDevicePolicyForWorkProfile() =
-        testScope.runTest {
+        kosmos.runTest {
             // Keyguard showing, and tutorial completed.
-            keyguardRepository.setKeyguardShowing(true)
-            keyguardRepository.setKeyguardOccluded(false)
-            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+            fakeKeyguardRepository.setKeyguardShowing(true)
+            fakeKeyguardRepository.setKeyguardOccluded(false)
+            fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
             val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
-            userRepository.setUserInfos(userInfos)
-            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
-            userRepository.setSelectedUserInfo(MAIN_USER_INFO)
-            runCurrent()
+            fakeUserRepository.setUserInfos(userInfos)
+            fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
+            fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
 
             val widgetContent by collectLastValue(underTest.widgetContent)
             // Given three widgets, and one of them is associated with work profile.
-            widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
-            widgetRepository.addPendingWidget(appWidgetId = 2, userId = USER_INFO_WORK.id)
-            widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
+            fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+            fakeCommunalWidgetRepository.addPendingWidget(
+                appWidgetId = 2,
+                userId = USER_INFO_WORK.id,
+            )
+            fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
 
             setKeyguardFeaturesDisabled(
                 USER_INFO_WORK,
@@ -973,7 +907,7 @@
 
     @Test
     fun showCommunalFromOccluded_enteredOccludedFromHub() =
-        testScope.runTest {
+        kosmos.runTest {
             kosmos.setCommunalAvailable(true)
             val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded)
             assertThat(showCommunalFromOccluded).isFalse()
@@ -989,7 +923,7 @@
 
     @Test
     fun showCommunalFromOccluded_enteredOccludedFromLockscreen() =
-        testScope.runTest {
+        kosmos.runTest {
             kosmos.setCommunalAvailable(true)
             val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded)
             assertThat(showCommunalFromOccluded).isFalse()
@@ -1005,7 +939,7 @@
 
     @Test
     fun showCommunalFromOccluded_communalBecomesUnavailableWhileOccluded() =
-        testScope.runTest {
+        kosmos.runTest {
             kosmos.setCommunalAvailable(true)
             val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded)
             assertThat(showCommunalFromOccluded).isFalse()
@@ -1015,7 +949,6 @@
                 to = KeyguardState.OCCLUDED,
                 testScope,
             )
-            runCurrent()
             kosmos.setCommunalAvailable(false)
 
             assertThat(showCommunalFromOccluded).isFalse()
@@ -1023,7 +956,7 @@
 
     @Test
     fun showCommunalFromOccluded_showBouncerWhileOccluded() =
-        testScope.runTest {
+        kosmos.runTest {
             kosmos.setCommunalAvailable(true)
             val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded)
             assertThat(showCommunalFromOccluded).isFalse()
@@ -1033,7 +966,6 @@
                 to = KeyguardState.OCCLUDED,
                 testScope,
             )
-            runCurrent()
             kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.OCCLUDED,
                 to = KeyguardState.PRIMARY_BOUNCER,
@@ -1045,7 +977,7 @@
 
     @Test
     fun showCommunalFromOccluded_enteredOccludedFromDreaming() =
-        testScope.runTest {
+        kosmos.runTest {
             kosmos.setCommunalAvailable(true)
             val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded)
             assertThat(showCommunalFromOccluded).isFalse()
@@ -1069,7 +1001,7 @@
 
     @Test
     fun dismissDisclaimerSetsDismissedFlag() =
-        testScope.runTest {
+        kosmos.runTest {
             val disclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed)
             assertThat(disclaimerDismissed).isFalse()
             underTest.setDisclaimerDismissed()
@@ -1078,17 +1010,17 @@
 
     @Test
     fun dismissDisclaimerTimeoutResetsDismissedFlag() =
-        testScope.runTest {
+        kosmos.runTest {
             val disclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed)
             underTest.setDisclaimerDismissed()
             assertThat(disclaimerDismissed).isTrue()
-            advanceTimeBy(CommunalInteractor.DISCLAIMER_RESET_MILLIS)
+            testScope.advanceTimeBy(CommunalInteractor.DISCLAIMER_RESET_MILLIS)
             assertThat(disclaimerDismissed).isFalse()
         }
 
     @Test
     fun settingSelectedKey_flowUpdated() {
-        testScope.runTest {
+        kosmos.runTest {
             val key = "test"
             val selectedKey by collectLastValue(underTest.selectedKey)
             underTest.setSelectedKey(key)
@@ -1098,36 +1030,35 @@
 
     @Test
     fun unpauseWorkProfileEnablesWorkMode() =
-        testScope.runTest {
+        kosmos.runTest {
             underTest.unpauseWorkProfile()
 
-            assertThat(managedProfileController.isWorkModeEnabled()).isTrue()
+            assertThat(fakeManagedProfileController.isWorkModeEnabled()).isTrue()
         }
 
     @Test
     @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING)
     @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
     fun resizeWidget_withoutUpdatingOrder() =
-        testScope.runTest {
+        kosmos.runTest {
             val userInfos = listOf(MAIN_USER_INFO)
-            userRepository.setUserInfos(userInfos)
-            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
-            runCurrent()
+            fakeUserRepository.setUserInfos(userInfos)
+            fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
 
             // Widgets available.
-            widgetRepository.addWidget(
+            fakeCommunalWidgetRepository.addWidget(
                 appWidgetId = 1,
                 userId = MAIN_USER_INFO.id,
                 rank = 0,
                 spanY = CommunalContentSize.FixedSize.HALF.span,
             )
-            widgetRepository.addWidget(
+            fakeCommunalWidgetRepository.addWidget(
                 appWidgetId = 2,
                 userId = MAIN_USER_INFO.id,
                 rank = 1,
                 spanY = CommunalContentSize.FixedSize.HALF.span,
             )
-            widgetRepository.addWidget(
+            fakeCommunalWidgetRepository.addWidget(
                 appWidgetId = 3,
                 userId = MAIN_USER_INFO.id,
                 rank = 2,
@@ -1159,26 +1090,25 @@
     @Test
     @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING, FLAG_COMMUNAL_RESPONSIVE_GRID)
     fun resizeWidget_withoutUpdatingOrder_responsive() =
-        testScope.runTest {
+        kosmos.runTest {
             val userInfos = listOf(MAIN_USER_INFO)
-            userRepository.setUserInfos(userInfos)
-            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
-            runCurrent()
+            fakeUserRepository.setUserInfos(userInfos)
+            fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
 
             // Widgets available.
-            widgetRepository.addWidget(
+            fakeCommunalWidgetRepository.addWidget(
                 appWidgetId = 1,
                 userId = MAIN_USER_INFO.id,
                 rank = 0,
                 spanY = 1,
             )
-            widgetRepository.addWidget(
+            fakeCommunalWidgetRepository.addWidget(
                 appWidgetId = 2,
                 userId = MAIN_USER_INFO.id,
                 rank = 1,
                 spanY = 1,
             )
-            widgetRepository.addWidget(
+            fakeCommunalWidgetRepository.addWidget(
                 appWidgetId = 3,
                 userId = MAIN_USER_INFO.id,
                 rank = 2,
@@ -1211,26 +1141,25 @@
     @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING)
     @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
     fun resizeWidget_andUpdateOrder() =
-        testScope.runTest {
+        kosmos.runTest {
             val userInfos = listOf(MAIN_USER_INFO)
-            userRepository.setUserInfos(userInfos)
-            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
-            runCurrent()
+            fakeUserRepository.setUserInfos(userInfos)
+            fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
 
             // Widgets available.
-            widgetRepository.addWidget(
+            fakeCommunalWidgetRepository.addWidget(
                 appWidgetId = 1,
                 userId = MAIN_USER_INFO.id,
                 rank = 0,
                 spanY = CommunalContentSize.FixedSize.HALF.span,
             )
-            widgetRepository.addWidget(
+            fakeCommunalWidgetRepository.addWidget(
                 appWidgetId = 2,
                 userId = MAIN_USER_INFO.id,
                 rank = 1,
                 spanY = CommunalContentSize.FixedSize.HALF.span,
             )
-            widgetRepository.addWidget(
+            fakeCommunalWidgetRepository.addWidget(
                 appWidgetId = 3,
                 userId = MAIN_USER_INFO.id,
                 rank = 2,
@@ -1266,26 +1195,25 @@
     @Test
     @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING, FLAG_COMMUNAL_RESPONSIVE_GRID)
     fun resizeWidget_andUpdateOrder_responsive() =
-        testScope.runTest {
+        kosmos.runTest {
             val userInfos = listOf(MAIN_USER_INFO)
-            userRepository.setUserInfos(userInfos)
-            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
-            runCurrent()
+            fakeUserRepository.setUserInfos(userInfos)
+            fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
 
             // Widgets available.
-            widgetRepository.addWidget(
+            fakeCommunalWidgetRepository.addWidget(
                 appWidgetId = 1,
                 userId = MAIN_USER_INFO.id,
                 rank = 0,
                 spanY = 1,
             )
-            widgetRepository.addWidget(
+            fakeCommunalWidgetRepository.addWidget(
                 appWidgetId = 2,
                 userId = MAIN_USER_INFO.id,
                 rank = 1,
                 spanY = 1,
             )
-            widgetRepository.addWidget(
+            fakeCommunalWidgetRepository.addWidget(
                 appWidgetId = 3,
                 userId = MAIN_USER_INFO.id,
                 rank = 2,
@@ -1318,6 +1246,66 @@
                 .inOrder()
         }
 
+    @Test
+    fun showCommunalWhileCharging() =
+        kosmos.runTest {
+            fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+            fakeUserRepository.setSelectedUserInfo(mainUser)
+            fakeKeyguardRepository.setKeyguardShowing(true)
+            fakeSettings.putIntForUser(
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+                1,
+                mainUser.id,
+            )
+
+            val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal)
+            batteryRepository.fake.setDevicePluggedIn(false)
+            assertThat(shouldShowCommunal).isFalse()
+
+            batteryRepository.fake.setDevicePluggedIn(true)
+            assertThat(shouldShowCommunal).isTrue()
+        }
+
+    @Test
+    fun showCommunalWhilePosturedAndCharging() =
+        kosmos.runTest {
+            fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+            fakeUserRepository.setSelectedUserInfo(mainUser)
+            fakeKeyguardRepository.setKeyguardShowing(true)
+            fakeSettings.putIntForUser(
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
+                1,
+                mainUser.id,
+            )
+
+            val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal)
+            batteryRepository.fake.setDevicePluggedIn(true)
+            posturingRepository.fake.setPosturedState(PosturedState.NotPostured)
+            assertThat(shouldShowCommunal).isFalse()
+
+            posturingRepository.fake.setPosturedState(PosturedState.Postured(1f))
+            assertThat(shouldShowCommunal).isTrue()
+        }
+
+    @Test
+    fun showCommunalWhileDocked() =
+        kosmos.runTest {
+            fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+            fakeUserRepository.setSelectedUserInfo(mainUser)
+            fakeKeyguardRepository.setKeyguardShowing(true)
+            fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, 1, mainUser.id)
+
+            batteryRepository.fake.setDevicePluggedIn(true)
+            fakeDockManager.setIsDocked(false)
+
+            val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal)
+            assertThat(shouldShowCommunal).isFalse()
+
+            fakeDockManager.setIsDocked(true)
+            fakeDockManager.setDockEvent(DockManager.STATE_DOCKED)
+            assertThat(shouldShowCommunal).isTrue()
+        }
+
     private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
         whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
             .thenReturn(disabledFlags)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt
index 1fef693..1f5f8ce 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt
@@ -108,6 +108,43 @@
             assertThat(isHubOnboardingDismissed).isFalse()
         }
 
+    @Test
+    fun setDreamButtonTooltipDismissed_currentUser() =
+        testScope.runTest {
+            setSelectedUser(MAIN_USER)
+            val isDreamButtonTooltipDismissed by
+                collectLastValue(underTest.isDreamButtonTooltipDismissed)
+
+            assertThat(isDreamButtonTooltipDismissed).isFalse()
+            underTest.setDreamButtonTooltipDismissed(MAIN_USER)
+            assertThat(isDreamButtonTooltipDismissed).isTrue()
+        }
+
+    @Test
+    fun setDreamButtonTooltipDismissed_anotherUser() =
+        testScope.runTest {
+            setSelectedUser(MAIN_USER)
+            val isDreamButtonTooltipDismissed by
+                collectLastValue(underTest.isDreamButtonTooltipDismissed)
+
+            assertThat(isDreamButtonTooltipDismissed).isFalse()
+            underTest.setDreamButtonTooltipDismissed(SECONDARY_USER)
+            assertThat(isDreamButtonTooltipDismissed).isFalse()
+        }
+
+    @Test
+    fun isDreamButtonTooltipDismissed_userSwitch() =
+        testScope.runTest {
+            setSelectedUser(MAIN_USER)
+            underTest.setDreamButtonTooltipDismissed(MAIN_USER)
+            val isDreamButtonTooltipDismissed by
+                collectLastValue(underTest.isDreamButtonTooltipDismissed)
+
+            assertThat(isDreamButtonTooltipDismissed).isTrue()
+            setSelectedUser(SECONDARY_USER)
+            assertThat(isDreamButtonTooltipDismissed).isFalse()
+        }
+
     private suspend fun setSelectedUser(user: UserInfo) {
         with(kosmos.fakeUserRepository) {
             setUserInfos(listOf(user))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt
index e4916b1..310bf64 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt
@@ -21,19 +21,17 @@
 import android.content.Intent
 import android.content.pm.UserInfo
 import android.os.UserManager
-import android.os.userManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.broadcastDispatcher
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.settings.fakeUserTracker
 import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.repository.fakeUserRepository
-import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertNull
@@ -48,34 +46,20 @@
 @RunWith(AndroidJUnit4::class)
 class CommunalSettingsInteractorTest : SysuiTestCase() {
 
-    private lateinit var userManager: UserManager
-    private lateinit var userRepository: FakeUserRepository
-    private lateinit var userTracker: FakeUserTracker
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
 
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-
-    private lateinit var underTest: CommunalSettingsInteractor
+    private val Kosmos.underTest by Kosmos.Fixture { communalSettingsInteractor }
 
     @Before
     fun setUp() {
-        userManager = kosmos.userManager
-        userRepository = kosmos.fakeUserRepository
-        userTracker = kosmos.fakeUserTracker
-
         val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
-        userRepository.setUserInfos(userInfos)
-        userTracker.set(
-            userInfos = userInfos,
-            selectedUserIndex = 0,
-        )
-
-        underTest = kosmos.communalSettingsInteractor
+        kosmos.fakeUserRepository.setUserInfos(userInfos)
+        kosmos.fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0)
     }
 
     @Test
     fun filterUsers_dontFilteredUsersWhenAllAreAllowed() =
-        testScope.runTest {
+        kosmos.runTest {
             // If no users have any keyguard features disabled...
             val disallowedUser by
                 collectLastValue(underTest.workProfileUserDisallowedByDevicePolicy)
@@ -85,11 +69,11 @@
 
     @Test
     fun filterUsers_filterWorkProfileUserWhenDisallowed() =
-        testScope.runTest {
+        kosmos.runTest {
             // If the work profile user has keyguard widgets disabled...
             setKeyguardFeaturesDisabled(
                 USER_INFO_WORK,
-                DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
+                DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL,
             )
             // ...then the disallowed user match the work profile
             val disallowedUser by
@@ -102,7 +86,7 @@
         whenever(
                 kosmos.devicePolicyManager.getKeyguardDisabledFeatures(
                     anyOrNull(),
-                    ArgumentMatchers.eq(user.id)
+                    ArgumentMatchers.eq(user.id),
                 )
             )
             .thenReturn(disabledFlags)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt
index 012ae8f..b747705 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal.ui.viewmodel
 
+import android.content.pm.UserInfo
 import android.platform.test.annotations.EnableFlags
 import android.provider.Settings
 import android.service.dream.dreamManager
@@ -24,15 +25,17 @@
 import com.android.internal.logging.uiEventLoggerFake
 import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalPrefsRepository
+import com.android.systemui.communal.domain.interactor.HubOnboardingInteractorTest.Companion.MAIN_USER
 import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
 import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.kosmos.collectLastValue
 import com.android.systemui.kosmos.runCurrent
 import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.plugins.activityStarter
+import com.android.systemui.settings.fakeUserTracker
 import com.android.systemui.statusbar.policy.batteryController
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.fakeUserRepository
@@ -52,7 +55,6 @@
 class CommunalToDreamButtonViewModelTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val uiEventLoggerFake = kosmos.uiEventLoggerFake
     private val underTest: CommunalToDreamButtonViewModel by lazy {
         kosmos.communalToDreamButtonViewModel
     }
@@ -68,9 +70,9 @@
         with(kosmos) {
             runTest {
                 whenever(batteryController.isPluggedIn()).thenReturn(true)
+                runCurrent()
 
-                val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub)
-                assertThat(shouldShowButton).isTrue()
+                assertThat(underTest.shouldShowDreamButtonOnHub).isTrue()
             }
         }
 
@@ -79,9 +81,9 @@
         with(kosmos) {
             runTest {
                 whenever(batteryController.isPluggedIn()).thenReturn(false)
+                runCurrent()
 
-                val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub)
-                assertThat(shouldShowButton).isFalse()
+                assertThat(underTest.shouldShowDreamButtonOnHub).isFalse()
             }
         }
 
@@ -124,6 +126,23 @@
         }
 
     @Test
+    fun shouldShowDreamButtonTooltip_trueWhenNotDismissed() =
+        kosmos.runTest {
+            runCurrent()
+            assertThat(underTest.shouldShowTooltip).isTrue()
+        }
+
+    @Test
+    fun shouldShowDreamButtonTooltip_falseWhenDismissed() =
+        kosmos.runTest {
+            setSelectedUser(MAIN_USER)
+            fakeCommunalPrefsRepository.setDreamButtonTooltipDismissed(MAIN_USER)
+            runCurrent()
+
+            assertThat(underTest.shouldShowTooltip).isFalse()
+        }
+
+    @Test
     fun onShowDreamButtonTap_eventLogged() =
         with(kosmos) {
             runTest {
@@ -134,4 +153,12 @@
                     .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_SHOW_DREAM_BUTTON_TAP.id)
             }
         }
+
+    private suspend fun setSelectedUser(user: UserInfo) {
+        with(kosmos.fakeUserRepository) {
+            setUserInfos(listOf(user))
+            setSelectedUserInfo(user)
+        }
+        kosmos.fakeUserTracker.set(userInfos = listOf(user), selectedUserIndex = 0)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
index 6c955bf..5fd480f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
@@ -176,14 +176,14 @@
         }
 
     @Test
-    fun nonPowerButtonFPS_coExFaceFailure_doNotVibrateError() =
+    fun nonPowerButtonFPS_coExFaceFailure_vibrateError() =
         testScope.runTest {
             val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
             enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC)
             enrollFace()
             runCurrent()
             faceFailure()
-            assertThat(playErrorHaptic).isNull()
+            assertThat(playErrorHaptic).isNotNull()
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index ef70305..af30e43 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -1149,7 +1149,7 @@
 
     @Test
     @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
-    fun skipsFaceErrorHaptics_nonSfps_coEx() =
+    fun playsFaceErrorHaptics_nonSfps_coEx() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
             val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)
@@ -1161,14 +1161,15 @@
             underTest.start()
             updateFaceAuthStatus(isSuccess = false)
 
-            assertThat(playErrorHaptic).isNull()
-            verify(vibratorHelper, never()).vibrateAuthError(anyString())
+            assertThat(playErrorHaptic).isNotNull()
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            verify(vibratorHelper).vibrateAuthError(anyString())
             verify(vibratorHelper, never()).vibrateAuthSuccess(anyString())
         }
 
     @Test
     @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
-    fun skipsMSDLFaceErrorHaptics_nonSfps_coEx() =
+    fun playsMSDLFaceErrorHaptics_nonSfps_coEx() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
             val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)
@@ -1180,9 +1181,10 @@
             underTest.start()
             updateFaceAuthStatus(isSuccess = false)
 
-            assertThat(playErrorHaptic).isNull()
-            assertThat(msdlPlayer.latestTokenPlayed).isNull()
-            assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+            assertThat(playErrorHaptic).isNotNull()
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE)
+            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 2020d0d..3d31787 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -460,17 +460,17 @@
     }
 
     @Test
-    public void testOnDisplayReady() {
-        mCommandQueue.onDisplayReady(DEFAULT_DISPLAY);
+    public void testonDisplayAddSystemDecorations() {
+        mCommandQueue.onDisplayAddSystemDecorations(DEFAULT_DISPLAY);
         waitForIdleSync();
-        verify(mCallbacks).onDisplayReady(eq(DEFAULT_DISPLAY));
+        verify(mCallbacks).onDisplayAddSystemDecorations(eq(DEFAULT_DISPLAY));
     }
 
     @Test
-    public void testOnDisplayReadyForSecondaryDisplay() {
-        mCommandQueue.onDisplayReady(SECONDARY_DISPLAY);
+    public void testonDisplayAddSystemDecorationsForSecondaryDisplay() {
+        mCommandQueue.onDisplayAddSystemDecorations(SECONDARY_DISPLAY);
         waitForIdleSync();
-        verify(mCallbacks).onDisplayReady(eq(SECONDARY_DISPLAY));
+        verify(mCallbacks).onDisplayAddSystemDecorations(eq(SECONDARY_DISPLAY));
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
index 39c42f1..28b2ee8d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
@@ -269,6 +269,36 @@
     }
 
     @Test
+    fun testOpenAndCloseGutsWithoutSave() {
+        val guts = spy(NotificationGuts(mContext))
+        whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock ->
+            handler.post(((invocation.arguments[0] as Runnable)))
+            null
+        }
+
+        // Test doesn't support animation since the guts view is not attached.
+        doNothing().whenever(guts).openControls(anyInt(), anyInt(), anyBoolean(), any())
+
+        val realRow = createTestNotificationRow()
+        val menuItem = createTestMenuItem(realRow)
+
+        val row = spy(realRow)
+        whenever(row.windowToken).thenReturn(Binder())
+        whenever(row.guts).thenReturn(guts)
+
+        assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
+        executor.runAllReady()
+        verify(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>())
+
+        gutsManager.closeAndUndoGuts()
+
+        verify(guts).closeControls(anyInt(), anyInt(), eq(false), eq(false))
+        verify(row, times(1)).setGutsView(any<MenuItem>())
+        executor.runAllReady()
+        verify(headsUpManager).setGutsShown(realRow.entry, false)
+    }
+
+    @Test
     fun testLockscreenShadeVisible_visible_gutsNotClosed() =
         testScope.runTest {
             // First, start out lockscreen or shade as not visible
@@ -377,52 +407,6 @@
         }
 
     @Test
-    fun testChangeDensityOrFontScale() {
-        val guts = spy(NotificationGuts(mContext))
-        whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock ->
-            handler.post(((invocation.arguments[0] as Runnable)))
-            null
-        }
-
-        // Test doesn't support animation since the guts view is not attached.
-        doNothing().whenever(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>())
-
-        val realRow = createTestNotificationRow()
-        val menuItem = createTestMenuItem(realRow)
-
-        val row = spy(realRow)
-
-        whenever(row.windowToken).thenReturn(Binder())
-        whenever(row.guts).thenReturn(guts)
-        doNothing().whenever(row).ensureGutsInflated()
-
-        val realEntry = realRow.entry
-        val entry = spy(realEntry)
-
-        whenever(entry.row).thenReturn(row)
-        whenever(entry.guts).thenReturn(guts)
-
-        assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
-        executor.runAllReady()
-        verify(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>())
-
-        // called once by mGutsManager.bindGuts() in mGutsManager.openGuts()
-        verify(row).setGutsView(any<MenuItem>())
-
-        row.onDensityOrFontScaleChanged()
-        gutsManager.onDensityOrFontScaleChanged(entry)
-
-        executor.runAllReady()
-
-        gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false)
-
-        verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean())
-
-        // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged()
-        verify(row, times(2)).setGutsView(any<MenuItem>())
-    }
-
-    @Test
     fun testAppOpsSettingsIntent_camera() {
         val row = createTestNotificationRow()
         val ops = ArraySet<Int>()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
index 6435e82..af67a04 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
@@ -16,29 +16,44 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
+import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
 import android.testing.TestableResources;
-import android.util.KeyValueListParser;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
 
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.AnimatorTestRule;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 import com.android.systemui.res.R;
 
+import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
+import java.util.List;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -46,8 +61,12 @@
 public class NotificationSnoozeTest extends SysuiTestCase {
     private static final int RES_DEFAULT = 2;
     private static final int[] RES_OPTIONS = {1, 2, 3};
-    private NotificationSnooze mNotificationSnooze;
-    private KeyValueListParser mMockParser;
+    private final NotificationSwipeActionHelper mSnoozeListener = mock(
+            NotificationSwipeActionHelper.class);
+    private NotificationSnooze mUnderTest;
+
+    @Rule
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this);
 
     @Before
     public void setUp() throws Exception {
@@ -56,62 +75,117 @@
         TestableResources resources = mContext.getOrCreateTestableResources();
         resources.addOverride(R.integer.config_notification_snooze_time_default, RES_DEFAULT);
         resources.addOverride(R.array.config_notification_snooze_times, RES_OPTIONS);
-        mNotificationSnooze = new NotificationSnooze(mContext, null);
-        mMockParser = mock(KeyValueListParser.class);
+
+        mUnderTest = new NotificationSnooze(mContext, null);
+        mUnderTest.setSnoozeListener(mSnoozeListener);
+        mUnderTest.mExpandButton = mock(ImageView.class);
+        mUnderTest.mSnoozeView = mock(View.class);
+        mUnderTest.mSelectedOptionText = mock(TextView.class);
+        mUnderTest.mDivider = mock(View.class);
+        mUnderTest.mSnoozeOptionContainer = mock(ViewGroup.class);
+        mUnderTest.mSnoozeOptions = mock(List.class);
+    }
+
+    @After
+    public void tearDown() {
+        // Make sure all animations are finished
+        mAnimatorTestRule.advanceTimeBy(1000L);
     }
 
     @Test
-    public void testGetOptionsWithNoConfig() throws Exception {
-        ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+    @EnableFlags(Flags.FLAG_NOTIFICATION_UNDO_GUTS_ON_CONFIG_CHANGED)
+    public void closeControls_withoutSave_performsUndo() {
+        ArrayList<SnoozeOption> options = mUnderTest.getDefaultSnoozeOptions();
+        mUnderTest.mSelectedOption = options.getFirst();
+        mUnderTest.showSnoozeOptions(true);
+
+        assertThat(
+                mUnderTest.handleCloseControls(/* save = */ false, /* force = */ false)).isFalse();
+
+        assertThat(mUnderTest.mSelectedOption).isNull();
+        assertThat(mUnderTest.isExpanded()).isFalse();
+        verify(mSnoozeListener, times(0)).snooze(any(), any());
+    }
+
+    @Test
+    public void closeControls_whenExpanded_collapsesOptions() {
+        ArrayList<SnoozeOption> options = mUnderTest.getDefaultSnoozeOptions();
+        mUnderTest.mSelectedOption = options.getFirst();
+        mUnderTest.showSnoozeOptions(true);
+
+        assertThat(mUnderTest.handleCloseControls(/* save = */ true, /* force = */ false)).isTrue();
+
+        assertThat(mUnderTest.mSelectedOption).isNotNull();
+        assertThat(mUnderTest.isExpanded()).isFalse();
+    }
+
+    @Test
+    public void closeControls_whenCollapsed_commitsChanges() {
+        ArrayList<SnoozeOption> options = mUnderTest.getDefaultSnoozeOptions();
+        mUnderTest.mSelectedOption = options.getFirst();
+
+        assertThat(mUnderTest.handleCloseControls(/* save = */ true, /* force = */ false)).isTrue();
+
+        verify(mSnoozeListener).snooze(any(), any());
+    }
+
+    @Test
+    public void closeControls_withForce_returnsFalse() {
+        assertThat(mUnderTest.handleCloseControls(/* save = */ true, /* force = */ true)).isFalse();
+    }
+
+    @Test
+    public void testGetOptionsWithNoConfig() {
+        ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions();
         assertEquals(3, result.size());
         assertEquals(1, result.get(0).getMinutesToSnoozeFor());  // respect order
         assertEquals(2, result.get(1).getMinutesToSnoozeFor());
         assertEquals(3, result.get(2).getMinutesToSnoozeFor());
-        assertEquals(2, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor());
+        assertEquals(2, mUnderTest.getDefaultOption().getMinutesToSnoozeFor());
     }
 
     @Test
-    public void testGetOptionsWithInvalidConfig() throws Exception {
+    public void testGetOptionsWithInvalidConfig() {
         Settings.Global.putString(mContext.getContentResolver(),
                 Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
                 "this is garbage");
-        ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+        ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions();
         assertEquals(3, result.size());
         assertEquals(1, result.get(0).getMinutesToSnoozeFor());  // respect order
         assertEquals(2, result.get(1).getMinutesToSnoozeFor());
         assertEquals(3, result.get(2).getMinutesToSnoozeFor());
-        assertEquals(2, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor());
+        assertEquals(2, mUnderTest.getDefaultOption().getMinutesToSnoozeFor());
     }
 
     @Test
-    public void testGetOptionsWithValidDefault() throws Exception {
+    public void testGetOptionsWithValidDefault() {
         Settings.Global.putString(mContext.getContentResolver(),
                 Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
                 "default=10,options_array=4:5:6:7");
-        ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
-        assertNotNull(mNotificationSnooze.getDefaultOption());  // pick one
+        ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions();
+        assertNotNull(mUnderTest.getDefaultOption());  // pick one
     }
 
     @Test
-    public void testGetOptionsWithValidConfig() throws Exception {
+    public void testGetOptionsWithValidConfig() {
         Settings.Global.putString(mContext.getContentResolver(),
                 Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
                 "default=6,options_array=4:5:6:7");
-        ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+        ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions();
         assertEquals(4, result.size());
         assertEquals(4, result.get(0).getMinutesToSnoozeFor());  // respect order
         assertEquals(5, result.get(1).getMinutesToSnoozeFor());
         assertEquals(6, result.get(2).getMinutesToSnoozeFor());
         assertEquals(7, result.get(3).getMinutesToSnoozeFor());
-        assertEquals(6, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor());
+        assertEquals(6, mUnderTest.getDefaultOption().getMinutesToSnoozeFor());
     }
 
     @Test
-    public void testGetOptionsWithLongConfig() throws Exception {
+    public void testGetOptionsWithLongConfig() {
         Settings.Global.putString(mContext.getContentResolver(),
                 Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
                 "default=6,options_array=4:5:6:7:8:9:10:11:12:13:14:15:16:17");
-        ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+        ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions();
         assertTrue(result.size() > 3);
         assertEquals(4, result.get(0).getMinutesToSnoozeFor());  // respect order
         assertEquals(5, result.get(1).getMinutesToSnoozeFor());
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
index 09be93d..ea91b7a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.content.res.Resources
 import android.hardware.devicestate.DeviceStateManager
+import android.os.PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.R
@@ -27,16 +28,20 @@
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.defaultDeviceState
 import com.android.systemui.deviceStateManager
-import com.android.systemui.display.data.repository.DeviceStateRepository
 import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.FOLDED
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.HALF_FOLDED
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.UNFOLDED
+import com.android.systemui.display.data.repository.fakeDeviceStateRepository
 import com.android.systemui.foldedDeviceStateList
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.power.shared.model.ScreenPowerState
-import com.android.systemui.power.shared.model.WakeSleepReason
-import com.android.systemui.power.shared.model.WakefulnessModel
-import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setScreenPowerState
+import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_OFF
+import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_ON
 import com.android.systemui.shared.system.SysUiStatsLog
 import com.android.systemui.statusbar.policy.FakeConfigurationController
 import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.FOLDABLE_DEVICE_STATE_CLOSED
@@ -45,7 +50,7 @@
 import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
 import com.android.systemui.unfoldedDeviceState
-import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
+import com.android.systemui.util.animation.data.repository.fakeAnimationStatusRepository
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.time.FakeSystemClock
@@ -77,14 +82,15 @@
     private lateinit var displaySwitchLatencyTracker: DisplaySwitchLatencyTracker
     @Captor private lateinit var loggerArgumentCaptor: ArgumentCaptor<DisplaySwitchLatencyEvent>
 
+    private val kosmos = Kosmos()
     private val mockContext = mock<Context>()
     private val resources = mock<Resources>()
-    private val foldStateRepository = mock<DeviceStateRepository>()
-    private val powerInteractor = mock<PowerInteractor>()
-    private val animationStatusRepository = mock<AnimationStatusRepository>()
+    private val foldStateRepository = kosmos.fakeDeviceStateRepository
+    private val powerInteractor = PowerInteractorFactory.create().powerInteractor
+    private val animationStatusRepository = kosmos.fakeAnimationStatusRepository
     private val keyguardInteractor = mock<KeyguardInteractor>()
     private val displaySwitchLatencyLogger = mock<DisplaySwitchLatencyLogger>()
-    private val kosmos = Kosmos()
+
     private val deviceStateManager = kosmos.deviceStateManager
     private val closedDeviceState = kosmos.foldedDeviceStateList.first()
     private val openDeviceState = kosmos.unfoldedDeviceState
@@ -94,12 +100,7 @@
 
     private val testDispatcher: TestDispatcher = StandardTestDispatcher()
     private val testScope: TestScope = TestScope(testDispatcher)
-    private val isAsleep = MutableStateFlow(false)
     private val isAodAvailable = MutableStateFlow(false)
-    private val deviceState = MutableStateFlow(DeviceState.UNFOLDED)
-    private val screenPowerState = MutableStateFlow(ScreenPowerState.SCREEN_ON)
-    private val areAnimationEnabled = MutableStateFlow(true)
-    private val lastWakefulnessEvent = MutableStateFlow(WakefulnessModel())
     private val systemClock = FakeSystemClock()
     private val configurationController = FakeConfigurationController()
     private val configurationRepository =
@@ -126,13 +127,10 @@
             .thenReturn(listOf(closedDeviceState, openDeviceState))
         whenever(resources.getIntArray(R.array.config_foldedDeviceStates))
             .thenReturn(nonEmptyClosedDeviceStatesArray)
-        whenever(foldStateRepository.state).thenReturn(deviceState)
-        whenever(powerInteractor.isAsleep).thenReturn(isAsleep)
-        whenever(animationStatusRepository.areAnimationsEnabled()).thenReturn(areAnimationEnabled)
-        whenever(powerInteractor.screenPowerState).thenReturn(screenPowerState)
         whenever(keyguardInteractor.isAodAvailable).thenReturn(isAodAvailable)
-        whenever(powerInteractor.detailedWakefulness).thenReturn(lastWakefulnessEvent)
-
+        animationStatusRepository.onAnimationStatusChanged(true)
+        powerInteractor.setAwakeForTest()
+        powerInteractor.setScreenPowerState(SCREEN_ON)
         displaySwitchLatencyTracker =
             DisplaySwitchLatencyTracker(
                 mockContext,
@@ -152,21 +150,19 @@
     @Test
     fun unfold_logsLatencyTillTransitionStarted() {
         testScope.runTest {
-            areAnimationEnabled.emit(true)
-
             displaySwitchLatencyTracker.start()
-            deviceState.emit(DeviceState.FOLDED)
-            screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+            setDeviceState(FOLDED)
+            powerInteractor.setScreenPowerState(SCREEN_OFF)
             systemClock.advanceTime(50)
             runCurrent()
-            deviceState.emit(DeviceState.HALF_FOLDED)
+            setDeviceState(HALF_FOLDED)
             runCurrent()
             systemClock.advanceTime(50)
-            screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+            powerInteractor.setScreenPowerState(SCREEN_ON)
             systemClock.advanceTime(200)
             unfoldTransitionProgressProvider.onTransitionStarted()
             runCurrent()
-            deviceState.emit(DeviceState.UNFOLDED)
+            setDeviceState(UNFOLDED)
 
             verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
             val loggedEvent = loggerArgumentCaptor.value
@@ -202,23 +198,22 @@
                     systemClock,
                     deviceStateManager,
                 )
-            areAnimationEnabled.emit(true)
 
             displaySwitchLatencyTracker.start()
-            deviceState.emit(DeviceState.FOLDED)
-            screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+            setDeviceState(FOLDED)
+            powerInteractor.setScreenPowerState(SCREEN_OFF)
             systemClock.advanceTime(50)
             runCurrent()
-            deviceState.emit(DeviceState.HALF_FOLDED)
+            setDeviceState(HALF_FOLDED)
             systemClock.advanceTime(50)
             runCurrent()
-            screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+            powerInteractor.setScreenPowerState(SCREEN_ON)
             systemClock.advanceTime(50)
             runCurrent()
             systemClock.advanceTime(200)
             unfoldTransitionProgressProvider.onTransitionStarted()
             runCurrent()
-            deviceState.emit(DeviceState.UNFOLDED)
+            setDeviceState(UNFOLDED)
 
             verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
             val loggedEvent = loggerArgumentCaptor.value
@@ -235,23 +230,23 @@
     @Test
     fun unfold_animationDisabled_logsLatencyTillScreenTurnedOn() {
         testScope.runTest {
-            areAnimationEnabled.emit(false)
+            animationStatusRepository.onAnimationStatusChanged(false)
 
             displaySwitchLatencyTracker.start()
-            deviceState.emit(DeviceState.FOLDED)
-            screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+            setDeviceState(FOLDED)
+            powerInteractor.setScreenPowerState(SCREEN_OFF)
             systemClock.advanceTime(50)
             runCurrent()
-            deviceState.emit(DeviceState.HALF_FOLDED)
+            setDeviceState(HALF_FOLDED)
             systemClock.advanceTime(50)
             runCurrent()
-            screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+            powerInteractor.setScreenPowerState(SCREEN_ON)
             systemClock.advanceTime(50)
             runCurrent()
             unfoldTransitionProgressProvider.onTransitionStarted()
             systemClock.advanceTime(200)
             runCurrent()
-            deviceState.emit(DeviceState.UNFOLDED)
+            setDeviceState(UNFOLDED)
 
             verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
             val loggedEvent = loggerArgumentCaptor.value
@@ -268,19 +263,18 @@
     @Test
     fun foldWhileStayingAwake_logsLatency() {
         testScope.runTest {
-            areAnimationEnabled.emit(true)
-            deviceState.emit(DeviceState.UNFOLDED)
-            screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+            setDeviceState(UNFOLDED)
+            powerInteractor.setScreenPowerState(SCREEN_ON)
 
             displaySwitchLatencyTracker.start()
-            deviceState.emit(DeviceState.HALF_FOLDED)
+            setDeviceState(HALF_FOLDED)
             systemClock.advanceTime(50)
             runCurrent()
-            deviceState.emit(DeviceState.FOLDED)
-            screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+            setDeviceState(FOLDED)
+            powerInteractor.setScreenPowerState(SCREEN_OFF)
             runCurrent()
             systemClock.advanceTime(200)
-            screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+            powerInteractor.setScreenPowerState(SCREEN_ON)
             runCurrent()
 
             verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
@@ -298,25 +292,19 @@
     @Test
     fun foldToAod_capturesToStateAsAod() {
         testScope.runTest {
-            areAnimationEnabled.emit(true)
-            deviceState.emit(DeviceState.UNFOLDED)
+            setDeviceState(UNFOLDED)
             isAodAvailable.emit(true)
 
             displaySwitchLatencyTracker.start()
-            deviceState.emit(DeviceState.HALF_FOLDED)
+            setDeviceState(HALF_FOLDED)
             systemClock.advanceTime(50)
             runCurrent()
-            deviceState.emit(DeviceState.FOLDED)
-            lastWakefulnessEvent.emit(
-                WakefulnessModel(
-                    internalWakefulnessState = WakefulnessState.ASLEEP,
-                    lastSleepReason = WakeSleepReason.FOLD,
-                )
-            )
-            screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+            setDeviceState(FOLDED)
+            powerInteractor.setAsleepForTest(sleepReason = GO_TO_SLEEP_REASON_DEVICE_FOLD)
+            powerInteractor.setScreenPowerState(SCREEN_OFF)
             runCurrent()
             systemClock.advanceTime(200)
-            screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+            powerInteractor.setScreenPowerState(SCREEN_ON)
             runCurrent()
 
             verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
@@ -335,22 +323,21 @@
     @Test
     fun fold_notAFoldable_shouldNotLogLatency() {
         testScope.runTest {
-            areAnimationEnabled.emit(true)
-            deviceState.emit(DeviceState.UNFOLDED)
+            setDeviceState(UNFOLDED)
             whenever(resources.getIntArray(R.array.config_foldedDeviceStates))
                 .thenReturn(IntArray(0))
             whenever(deviceStateManager.supportedDeviceStates)
                 .thenReturn(listOf(defaultDeviceState))
 
             displaySwitchLatencyTracker.start()
-            deviceState.emit(DeviceState.HALF_FOLDED)
+            setDeviceState(HALF_FOLDED)
             systemClock.advanceTime(50)
             runCurrent()
-            deviceState.emit(DeviceState.FOLDED)
-            screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+            setDeviceState(FOLDED)
+            powerInteractor.setScreenPowerState(SCREEN_OFF)
             runCurrent()
             systemClock.advanceTime(200)
-            screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+            powerInteractor.setScreenPowerState(SCREEN_ON)
             runCurrent()
 
             verify(displaySwitchLatencyLogger, never()).log(any())
@@ -360,22 +347,16 @@
     @Test
     fun foldToScreenOff_capturesToStateAsScreenOff() {
         testScope.runTest {
-            areAnimationEnabled.emit(true)
-            deviceState.emit(DeviceState.UNFOLDED)
+            setDeviceState(UNFOLDED)
             isAodAvailable.emit(false)
 
             displaySwitchLatencyTracker.start()
-            deviceState.emit(DeviceState.HALF_FOLDED)
+            setDeviceState(HALF_FOLDED)
             systemClock.advanceTime(50)
             runCurrent()
-            deviceState.emit(DeviceState.FOLDED)
-            lastWakefulnessEvent.emit(
-                WakefulnessModel(
-                    internalWakefulnessState = WakefulnessState.ASLEEP,
-                    lastSleepReason = WakeSleepReason.FOLD,
-                )
-            )
-            screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+            setDeviceState(FOLDED)
+            powerInteractor.setAsleepForTest(sleepReason = GO_TO_SLEEP_REASON_DEVICE_FOLD)
+            powerInteractor.setScreenPowerState(SCREEN_OFF)
             runCurrent()
 
             verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
@@ -390,4 +371,8 @@
             assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
         }
     }
+
+    private suspend fun setDeviceState(state: DeviceState) {
+        foldStateRepository.emit(state)
+    }
 }
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5373b9d..fc9635b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1365,6 +1365,8 @@
     <string name="hub_onboarding_bottom_sheet_text">Access your favorite widgets and screen savers while charging.</string>
     <!-- Hub onboarding bottom sheet action button title. [CHAR LIMIT=NONE] -->
     <string name="hub_onboarding_bottom_sheet_action_button">Let\u2019s go</string>
+    <!-- Text for a tooltip that appears over the "show screensaver" button on glanceable hub. [CHAR LIMIT=NONE] -->
+    <string name="glanceable_hub_to_dream_button_tooltip">Show your favorite screensavers while charging</string>
 
     <!-- Related to user switcher --><skip/>
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
index b43ffc5..10b9303 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
@@ -146,9 +146,9 @@
     void onUnbind(IRemoteCallback reply) = 35;
 
     /**
-     * Sent when {@link TaskbarDelegate#onDisplayReady} is called.
+     * Sent when {@link TaskbarDelegate#onDisplayAddSystemDecorations} is called.
      */
-    void onDisplayReady(int displayId) = 36;
+    void onDisplayAddSystemDecorations(int displayId) = 36;
 
     /**
      * Sent when {@link TaskbarDelegate#onDisplayRemoved} is called.
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index e76f38c..9507b04 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -62,15 +62,14 @@
                 scope,
                 mainDispatcher,
                 bgDispatcher,
-                com.android.systemui.Flags.lockscreenCustomClocks()
+                com.android.systemui.shared.Flags.lockscreenCustomClocks()
                         || featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
                 /* handleAllUsers= */ true,
                 new DefaultClockProvider(
                         context,
                         layoutInflater,
                         resources,
-
-                        com.android.systemui.Flags.clockReactiveVariants()
+                        com.android.systemui.shared.Flags.clockReactiveVariants()
                 ),
                 context.getString(R.string.lockscreen_clock_id_fallback),
                 clockBuffers,
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 1c99473..1264712 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -32,6 +32,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.scene.domain.SceneFrameworkTableLog
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
@@ -66,6 +69,7 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val repository: AuthenticationRepository,
     private val selectedUserInteractor: SelectedUserInteractor,
+    @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer,
 ) {
     /**
      * The currently-configured authentication method. This determines how the authentication
@@ -85,7 +89,11 @@
      * `true` even when the lockscreen is showing and still needs to be dismissed by the user to
      * proceed.
      */
-    val authenticationMethod: Flow<AuthenticationMethodModel> = repository.authenticationMethod
+    val authenticationMethod: Flow<AuthenticationMethodModel> =
+        repository.authenticationMethod.logDiffsForTable(
+            tableLogBuffer = tableLogBuffer,
+            initialValue = AuthenticationMethodModel.None,
+        )
 
     /**
      * Whether the auto confirm feature is enabled for the currently-selected user.
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
index 4e45fcc..744fd7e 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
@@ -16,6 +16,9 @@
 
 package com.android.systemui.authentication.shared.model
 
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
 /** Enumerates all known authentication methods. */
 sealed class AuthenticationMethodModel(
     /**
@@ -24,8 +27,8 @@
      * "Secure" authentication methods require authentication to unlock the device. Non-secure auth
      * methods simply require user dismissal.
      */
-    open val isSecure: Boolean,
-) {
+    open val isSecure: Boolean
+) : Diffable<AuthenticationMethodModel> {
     /**
      * Device doesn't use a secure authentication method. Either there is no lockscreen or the lock
      * screen can be swiped away when displayed.
@@ -39,4 +42,8 @@
     data object Pattern : AuthenticationMethodModel(isSecure = true)
 
     data object Sim : AuthenticationMethodModel(isSecure = true)
+
+    override fun logDiffs(prevVal: AuthenticationMethodModel, row: TableRowLogger) {
+        row.logChange(columnName = "authenticationMethod", value = toString())
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
index 1923880..79e66a8 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.common.data
 
+import com.android.systemui.common.data.repository.BatteryRepository
+import com.android.systemui.common.data.repository.BatteryRepositoryImpl
 import com.android.systemui.common.data.repository.PackageChangeRepository
 import com.android.systemui.common.data.repository.PackageChangeRepositoryImpl
 import dagger.Binds
@@ -27,4 +29,6 @@
     abstract fun bindPackageChangeRepository(
         impl: PackageChangeRepositoryImpl
     ): PackageChangeRepository
+
+    @Binds abstract fun bindBatteryRepository(impl: BatteryRepositoryImpl): BatteryRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/BatteryRepository.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/BatteryRepository.kt
new file mode 100644
index 0000000..63b0513
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/BatteryRepository.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2025 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.common.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.kotlin.isDevicePluggedIn
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.stateIn
+
+interface BatteryRepository {
+    val isDevicePluggedIn: Flow<Boolean>
+}
+
+@SysUISingleton
+class BatteryRepositoryImpl
+@Inject
+constructor(@Background bgScope: CoroutineScope, batteryController: BatteryController) :
+    BatteryRepository {
+
+    /** Returns {@code true} if the device is currently plugged in or wireless charging. */
+    override val isDevicePluggedIn: Flow<Boolean> =
+        batteryController
+            .isDevicePluggedIn()
+            .stateIn(bgScope, SharingStarted.WhileSubscribed(), batteryController.isPluggedIn)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/BatteryInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/BatteryInteractor.kt
new file mode 100644
index 0000000..987776d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/BatteryInteractor.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2025 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.common.domain.interactor
+
+import com.android.systemui.common.data.repository.BatteryRepository
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class BatteryInteractor @Inject constructor(batteryRepository: BatteryRepository) {
+    val isDevicePluggedIn = batteryRepository.isDevicePluggedIn
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingListener.kt b/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingListener.kt
index 47040fa..af8a5fa2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingListener.kt
@@ -58,7 +58,6 @@
             .distinctUntilChanged()
             .logDiffsForTable(
                 tableLogBuffer = tableLogBuffer,
-                columnPrefix = "",
                 columnName = "postured",
                 initialValue = false,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index 882991aa..b89d322 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -52,7 +52,6 @@
     override val mediaModel: Flow<CommunalMediaModel> =
         _mediaModel.logDiffsForTable(
             tableLogBuffer = tableLogBuffer,
-            columnPrefix = "",
             initialValue = CommunalMediaModel.INACTIVE,
         )
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
index 0902646..b747690 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -56,6 +56,12 @@
 
     /** Save the hub onboarding dismissed state for the current user. */
     suspend fun setHubOnboardingDismissed(user: UserInfo)
+
+    /** Whether dream button tooltip has been dismissed. */
+    fun isDreamButtonTooltipDismissed(user: UserInfo): Flow<Boolean>
+
+    /** Save the dream button tooltip dismissed state for the current user. */
+    suspend fun setDreamButtonTooltipDismissed(user: UserInfo)
 }
 
 @SysUISingleton
@@ -87,27 +93,34 @@
         readKeyForUser(user, CTA_DISMISSED_STATE)
 
     override suspend fun setCtaDismissed(user: UserInfo) =
-        withContext(bgDispatcher) {
-            getSharedPrefsForUser(user).edit().putBoolean(CTA_DISMISSED_STATE, true).apply()
-            logger.i("Dismissed CTA tile")
-        }
+        setBooleanKeyValueForUser(user, CTA_DISMISSED_STATE, "Dismissed CTA tile")
 
     override fun isHubOnboardingDismissed(user: UserInfo): Flow<Boolean> =
         readKeyForUser(user, HUB_ONBOARDING_DISMISSED_STATE)
 
     override suspend fun setHubOnboardingDismissed(user: UserInfo) =
-        withContext(bgDispatcher) {
-            getSharedPrefsForUser(user)
-                .edit()
-                .putBoolean(HUB_ONBOARDING_DISMISSED_STATE, true)
-                .apply()
-            logger.i("Dismissed hub onboarding")
-        }
+        setBooleanKeyValueForUser(user, HUB_ONBOARDING_DISMISSED_STATE, "Dismissed hub onboarding")
+
+    override fun isDreamButtonTooltipDismissed(user: UserInfo): Flow<Boolean> =
+        readKeyForUser(user, DREAM_BUTTON_TOOLTIP_DISMISSED_STATE)
+
+    override suspend fun setDreamButtonTooltipDismissed(user: UserInfo) =
+        setBooleanKeyValueForUser(
+            user,
+            DREAM_BUTTON_TOOLTIP_DISMISSED_STATE,
+            "Dismissed dream button tooltip",
+        )
 
     private fun getSharedPrefsForUser(user: UserInfo): SharedPreferences {
         return userFileManager.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, user.id)
     }
 
+    private suspend fun setBooleanKeyValueForUser(user: UserInfo, key: String, logMsg: String) =
+        withContext(bgDispatcher) {
+            getSharedPrefsForUser(user).edit().putBoolean(key, true).apply()
+            logger.i(logMsg)
+        }
+
     private fun readKeyForUser(user: UserInfo, key: String): Flow<Boolean> {
         return backupRestorationEvents
             .flatMapLatest {
@@ -122,5 +135,6 @@
         const val FILE_NAME = "communal_hub_prefs"
         const val CTA_DISMISSED_STATE = "cta_dismissed"
         const val HUB_ONBOARDING_DISMISSED_STATE = "hub_onboarding_dismissed"
+        const val DREAM_BUTTON_TOOLTIP_DISMISSED_STATE = "dream_button_tooltip_dismissed_state"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index 53122c5..abd1016 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_USER_SETTING
 import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule.Companion.DEFAULT_BACKGROUND_TYPE
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
+import com.android.systemui.communal.shared.model.WhenToDream
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -60,6 +61,12 @@
     fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean>
 
     /**
+     * Returns a [WhenToDream] for the specified user, indicating what state the device should be in
+     * to trigger dreams.
+     */
+    fun getWhenToDreamState(user: UserInfo): Flow<WhenToDream>
+
+    /**
      * Returns true if any glanceable hub functionality should be enabled via configs and flags.
      *
      * This should be used for preventing basic glanceable hub functionality from running on devices
@@ -157,6 +164,49 @@
             }
             .flowOn(bgDispatcher)
 
+    override fun getWhenToDreamState(user: UserInfo): Flow<WhenToDream> =
+        secureSettings
+            .observerFlow(
+                userId = user.id,
+                names =
+                    arrayOf(
+                        Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+                        Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+                        Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
+                    ),
+            )
+            .emitOnStart()
+            .map {
+                if (
+                    secureSettings.getIntForUser(
+                        Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+                        0,
+                        user.id,
+                    ) == 1
+                ) {
+                    WhenToDream.WHILE_CHARGING
+                } else if (
+                    secureSettings.getIntForUser(
+                        Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+                        0,
+                        user.id,
+                    ) == 1
+                ) {
+                    WhenToDream.WHILE_DOCKED
+                } else if (
+                    secureSettings.getIntForUser(
+                        Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
+                        0,
+                        user.id,
+                    ) == 1
+                ) {
+                    WhenToDream.WHILE_POSTURED
+                } else {
+                    WhenToDream.NEVER
+                }
+            }
+            .flowOn(bgDispatcher)
+
     override fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> =
         broadcastDispatcher
             .broadcastFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
index 19666e4..1b0a6a0 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
@@ -98,7 +98,6 @@
             .filterNotNull()
             .logDiffsForTable(
                 tableLogBuffer = tableLogBuffer,
-                columnPrefix = "",
                 columnName = "tutorialSettingState",
                 initialValue = HUB_MODE_TUTORIAL_NOT_STARTED,
             )
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 74c335e..b4e6e93 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
@@ -30,11 +30,13 @@
 import com.android.systemui.Flags.communalResponsiveGrid
 import com.android.systemui.Flags.glanceableHubBlurredBackground
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.domain.interactor.BatteryInteractor
 import com.android.systemui.communal.data.repository.CommunalMediaRepository
 import com.android.systemui.communal.data.repository.CommunalSmartspaceRepository
 import com.android.systemui.communal.data.repository.CommunalWidgetRepository
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent
+import com.android.systemui.communal.posturing.domain.interactor.PosturingInteractor
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.FULL
@@ -43,11 +45,14 @@
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.shared.model.EditModeState
+import com.android.systemui.communal.shared.model.WhenToDream
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dock.DockManager
+import com.android.systemui.dock.retrieveIsDocked
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.Edge
@@ -67,6 +72,7 @@
 import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import com.android.systemui.util.kotlin.emitOnStart
+import com.android.systemui.util.kotlin.isDevicePluggedIn
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.minutes
 import kotlinx.coroutines.CoroutineDispatcher
@@ -86,6 +92,7 @@
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
@@ -117,6 +124,9 @@
     @CommunalLog logBuffer: LogBuffer,
     @CommunalTableLog tableLogBuffer: TableLogBuffer,
     private val managedProfileController: ManagedProfileController,
+    private val batteryInteractor: BatteryInteractor,
+    private val dockManager: DockManager,
+    private val posturingInteractor: PosturingInteractor,
 ) {
     private val logger = Logger(logBuffer, "CommunalInteractor")
 
@@ -163,7 +173,6 @@
             }
             .logDiffsForTable(
                 tableLogBuffer = tableLogBuffer,
-                columnPrefix = "",
                 columnName = "isCommunalAvailable",
                 initialValue = false,
             )
@@ -173,6 +182,33 @@
                 replay = 1,
             )
 
+    /**
+     * Whether communal hub should be shown automatically, depending on the user's [WhenToDream]
+     * state.
+     */
+    val shouldShowCommunal: Flow<Boolean> =
+        allOf(
+            isCommunalAvailable,
+            communalSettingsInteractor.whenToDream
+                .flatMapLatest { whenToDream ->
+                    when (whenToDream) {
+                        WhenToDream.NEVER -> flowOf(false)
+
+                        WhenToDream.WHILE_CHARGING -> batteryInteractor.isDevicePluggedIn
+
+                        WhenToDream.WHILE_DOCKED ->
+                            allOf(
+                                batteryInteractor.isDevicePluggedIn,
+                                dockManager.retrieveIsDocked(),
+                            )
+
+                        WhenToDream.WHILE_POSTURED ->
+                            allOf(batteryInteractor.isDevicePluggedIn, posturingInteractor.postured)
+                    }
+                }
+                .flowOn(bgDispatcher),
+        )
+
     private val _isDisclaimerDismissed = MutableStateFlow(false)
     val isDisclaimerDismissed: Flow<Boolean> = _isDisclaimerDismissed.asStateFlow()
 
@@ -300,7 +336,6 @@
             }
             .logDiffsForTable(
                 tableLogBuffer = tableLogBuffer,
-                columnPrefix = "",
                 columnName = "isCommunalShowing",
                 initialValue = false,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt
index ec45d6c..cdf1703 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt
@@ -49,7 +49,6 @@
             .flatMapLatest { user -> repository.isCtaDismissed(user) }
             .logDiffsForTable(
                 tableLogBuffer = tableLogBuffer,
-                columnPrefix = "",
                 columnName = "isCtaDismissed",
                 initialValue = false,
             )
@@ -67,7 +66,6 @@
             .flatMapLatest { user -> repository.isHubOnboardingDismissed(user) }
             .logDiffsForTable(
                 tableLogBuffer = tableLogBuffer,
-                columnPrefix = "",
                 columnName = "isHubOnboardingDismissed",
                 initialValue = false,
             )
@@ -80,6 +78,24 @@
     fun setHubOnboardingDismissed(user: UserInfo = userTracker.userInfo) =
         bgScope.launch { repository.setHubOnboardingDismissed(user) }
 
+    val isDreamButtonTooltipDismissed: Flow<Boolean> =
+        userInteractor.selectedUserInfo
+            .flatMapLatest { user -> repository.isDreamButtonTooltipDismissed(user) }
+            .logDiffsForTable(
+                tableLogBuffer = tableLogBuffer,
+                columnPrefix = "",
+                columnName = "isDreamButtonTooltipDismissed",
+                initialValue = false,
+            )
+            .stateIn(
+                scope = bgScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    fun setDreamButtonTooltipDismissed(user: UserInfo = userTracker.userInfo) =
+        bgScope.launch { repository.setDreamButtonTooltipDismissed(user) }
+
     private companion object {
         const val TAG = "CommunalPrefsInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
index 1738f37..a0b1261 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.communal.data.model.CommunalEnabledState
 import com.android.systemui.communal.data.repository.CommunalSettingsRepository
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
+import com.android.systemui.communal.shared.model.WhenToDream
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.log.dagger.CommunalTableLog
@@ -73,6 +74,12 @@
             repository.getScreensaverEnabledState(user)
         }
 
+    /** When to dream for the currently selected user. */
+    val whenToDream: Flow<WhenToDream> =
+        userInteractor.selectedUserInfo.flatMapLatest { user ->
+            repository.getWhenToDreamState(user)
+        }
+
     /**
      * Returns true if any glanceable hub functionality should be enabled via configs and flags.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
index 4e4ecc9..8f55d96 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
@@ -65,7 +65,6 @@
             }
             .logDiffsForTable(
                 tableLogBuffer = tableLogBuffer,
-                columnPrefix = "",
                 columnName = "isTutorialAvailable",
                 initialValue = false,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/WhenToDream.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/WhenToDream.kt
new file mode 100644
index 0000000..0d4eb60c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/WhenToDream.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2025 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.model
+
+enum class WhenToDream {
+    NEVER,
+    WHILE_CHARGING,
+    WHILE_DOCKED,
+    WHILE_POSTURED,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt
index bbb1686..7e683c4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt
@@ -20,11 +20,14 @@
 import android.app.DreamManager
 import android.content.Intent
 import android.provider.Settings
+import androidx.compose.runtime.getValue
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.communal.domain.interactor.CommunalPrefsInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.util.kotlin.isDevicePluggedIn
@@ -37,7 +40,7 @@
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.receiveAsFlow
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
@@ -47,18 +50,36 @@
 constructor(
     @Background private val backgroundContext: CoroutineContext,
     batteryController: BatteryController,
+    private val prefsInteractor: CommunalPrefsInteractor,
     private val settingsInteractor: CommunalSettingsInteractor,
     private val activityStarter: ActivityStarter,
     private val dreamManager: DreamManager,
     private val uiEventLogger: UiEventLogger,
 ) : ExclusiveActivatable() {
 
+    private val hydrator = Hydrator("CommunalToDreamButtonViewModel.hydrator")
     private val _requests = Channel<Unit>(Channel.BUFFERED)
 
     /** Whether we should show a button on hub to switch to dream. */
-    @SuppressLint("MissingPermission")
-    val shouldShowDreamButtonOnHub =
-        batteryController.isDevicePluggedIn().distinctUntilChanged().flowOn(backgroundContext)
+    val shouldShowDreamButtonOnHub: Boolean by
+        hydrator.hydratedStateOf(
+            traceName = "shouldShowDreamButtonOnHub",
+            initialValue = false,
+            source = batteryController.isDevicePluggedIn().distinctUntilChanged(),
+        )
+
+    /** Return whether the dream button tooltip has been dismissed. */
+    val shouldShowTooltip: Boolean by
+        hydrator.hydratedStateOf(
+            traceName = "shouldShowTooltip",
+            initialValue = false,
+            source = prefsInteractor.isDreamButtonTooltipDismissed.map { !it },
+        )
+
+    /** Set the dream button tooltip to be dismissed. */
+    fun setDreamButtonTooltipDismissed() {
+        prefsInteractor.setDreamButtonTooltipDismissed()
+    }
 
     /** Handle a tap on the "show dream" button. */
     fun onShowDreamButtonTap() {
@@ -86,6 +107,8 @@
                 }
         }
 
+        launch { hydrator.activate() }
+
         awaitCancellation()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
index 1e7bec2..69da67e 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
@@ -68,4 +68,10 @@
                 emptyFlow()
             }
         }
+
+    /** Triggered if a face failure occurs regardless of the mode. */
+    val faceFailure: Flow<FailedFaceAuthenticationStatus> =
+        deviceEntryFaceAuthInteractor.authenticationStatus.filterIsInstance<
+            FailedFaceAuthenticationStatus
+        >()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
index cdd2b05..079d624 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
 import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
+import com.android.systemui.log.table.TableLogBuffer
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 
@@ -81,6 +82,8 @@
 
     /** Whether face auth is considered class 3 */
     fun isFaceAuthStrong(): Boolean
+
+    suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer)
 }
 
 /**
@@ -93,17 +96,17 @@
  */
 interface FaceAuthenticationListener {
     /** Receive face isAuthenticated updates */
-    fun onAuthenticatedChanged(isAuthenticated: Boolean)
+    fun onAuthenticatedChanged(isAuthenticated: Boolean) = Unit
 
     /** Receive face authentication status updates */
-    fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus)
+    fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus) = Unit
 
     /** Receive status updates whenever face detection runs */
-    fun onDetectionStatusChanged(status: FaceDetectionStatus)
+    fun onDetectionStatusChanged(status: FaceDetectionStatus) = Unit
 
-    fun onLockoutStateChanged(isLockedOut: Boolean)
+    fun onLockoutStateChanged(isLockedOut: Boolean) = Unit
 
-    fun onRunningStateChanged(isRunning: Boolean)
+    fun onRunningStateChanged(isRunning: Boolean) = Unit
 
-    fun onAuthEnrollmentStateChanged(enrolled: Boolean)
+    fun onAuthEnrollmentStateChanged(enrolled: Boolean) = Unit
 }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
index cd456a6..38e0503 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
@@ -123,7 +123,7 @@
     private val playErrorHapticForBiometricFailure: Flow<Unit> =
         merge(
                 deviceEntryFingerprintAuthInteractor.fingerprintFailure,
-                deviceEntryBiometricAuthInteractor.faceOnlyFaceFailure,
+                deviceEntryBiometricAuthInteractor.faceFailure,
             )
             // map to Unit
             .map {}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 4ddc98c..5b68597 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -25,7 +25,10 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
 import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.scene.data.model.asIterable
+import com.android.systemui.scene.domain.SceneFrameworkTableLog
 import com.android.systemui.scene.domain.interactor.SceneBackInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
@@ -33,9 +36,11 @@
 import com.android.systemui.utils.coroutines.flow.mapLatestConflated
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
@@ -43,6 +48,7 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /**
  * Hosts application business logic related to device entry.
@@ -62,6 +68,7 @@
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
     private val dismissCallbackRegistry: DismissCallbackRegistry,
     sceneBackInteractor: SceneBackInteractor,
+    @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer,
 ) {
     /**
      * Whether the device is unlocked.
@@ -147,6 +154,11 @@
             ) { enteredDirectly, enteredOnBackStack ->
                 enteredOnBackStack || enteredDirectly
             }
+            .logDiffsForTable(
+                tableLogBuffer = tableLogBuffer,
+                columnName = "isDeviceEntered",
+                initialValue = false,
+            )
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
@@ -184,6 +196,11 @@
                         deviceUnlockStatus.deviceUnlockSource?.dismissesLockscreen == false)) &&
                     !isDeviceEntered
             }
+            .logDiffsForTable(
+                tableLogBuffer = tableLogBuffer,
+                columnName = "canSwipeToEnter",
+                initialValue = false,
+            )
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
@@ -271,4 +288,29 @@
     fun lockNow() {
         deviceUnlockedInteractor.lockNow()
     }
+
+    suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) {
+        coroutineScope {
+            launch {
+                isDeviceEntered
+                    .logDiffsForTable(
+                        tableLogBuffer = tableLogBuffer,
+                        columnName = "isDeviceEntered",
+                        initialValue = isDeviceEntered.value,
+                    )
+                    .collect()
+            }
+
+            launch {
+                canSwipeToEnter
+                    .map { it?.toString() ?: "" }
+                    .logDiffsForTable(
+                        tableLogBuffer = tableLogBuffer,
+                        columnName = "canSwipeToEnter",
+                        initialValue = canSwipeToEnter.value?.toString() ?: "",
+                    )
+                    .collect()
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index 68aef52..b1be9a2 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -33,8 +33,11 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.TrustInteractor
 import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.scene.domain.SceneFrameworkTableLog
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
 import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
@@ -48,6 +51,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -74,6 +78,7 @@
     private val systemPropertiesHelper: SystemPropertiesHelper,
     private val userAwareSecureSettingsRepository: UserAwareSecureSettingsRepository,
     private val keyguardInteractor: KeyguardInteractor,
+    @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer,
 ) : ExclusiveActivatable() {
 
     private val deviceUnlockSource =
@@ -179,17 +184,33 @@
     private val lockNowRequests = Channel<Unit>()
 
     override suspend fun onActivated(): Nothing {
-        authenticationInteractor.authenticationMethod.collectLatest { authMethod ->
-            if (!authMethod.isSecure) {
-                // Device remains unlocked as long as the authentication method is not secure.
-                Log.d(TAG, "remaining unlocked because auth method not secure")
-                repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, null)
-            } else if (authMethod == AuthenticationMethodModel.Sim) {
-                // Device remains locked while SIM is locked.
-                Log.d(TAG, "remaining locked because SIM locked")
-                repository.deviceUnlockStatus.value = DeviceUnlockStatus(false, null)
-            } else {
-                handleLockAndUnlockEvents()
+        coroutineScope {
+            launch {
+                authenticationInteractor.authenticationMethod.collectLatest { authMethod ->
+                    if (!authMethod.isSecure) {
+                        // Device remains unlocked as long as the authentication method is not
+                        // secure.
+                        Log.d(TAG, "remaining unlocked because auth method not secure")
+                        repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, null)
+                    } else if (authMethod == AuthenticationMethodModel.Sim) {
+                        // Device remains locked while SIM is locked.
+                        Log.d(TAG, "remaining locked because SIM locked")
+                        repository.deviceUnlockStatus.value = DeviceUnlockStatus(false, null)
+                    } else {
+                        handleLockAndUnlockEvents()
+                    }
+                }
+            }
+
+            launch {
+                deviceUnlockStatus
+                    .map { it.isUnlocked }
+                    .logDiffsForTable(
+                        tableLogBuffer = tableLogBuffer,
+                        columnName = "isUnlocked",
+                        initialValue = deviceUnlockStatus.value.isUnlocked,
+                    )
+                    .collect()
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
index 9b8c2b1..ecc4dbc 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
 import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
+import com.android.systemui.log.table.TableLogBuffer
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -73,4 +74,6 @@
     override fun onWalletLaunched() = Unit
 
     override fun onDeviceUnfolded() {}
+
+    override suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index b19b2d9..4b90e1d 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -44,6 +44,8 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -53,13 +55,16 @@
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.kotlin.pairwise
 import com.android.systemui.util.kotlin.sample
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNotNull
@@ -379,6 +384,27 @@
             .launchIn(applicationScope)
     }
 
+    override suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) {
+        conflatedCallbackFlow {
+                val listener =
+                    object : FaceAuthenticationListener {
+                        override fun onAuthEnrollmentStateChanged(enrolled: Boolean) {
+                            trySend(isFaceAuthEnabledAndEnrolled())
+                        }
+                    }
+
+                registerListener(listener)
+
+                awaitClose { unregisterListener(listener) }
+            }
+            .logDiffsForTable(
+                tableLogBuffer = tableLogBuffer,
+                columnName = "isFaceAuthEnabledAndEnrolled",
+                initialValue = isFaceAuthEnabledAndEnrolled(),
+            )
+            .collect()
+    }
+
     companion object {
         const val TAG = "DeviceEntryFaceAuthInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 8c60371..cf712f1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -19,8 +19,12 @@
 import android.animation.ValueAnimator
 import android.util.MathUtils
 import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.Flags.communalSceneKtfRefactor
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+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
@@ -39,6 +43,10 @@
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -47,11 +55,6 @@
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
-import kotlin.time.Duration.Companion.milliseconds
-import kotlin.time.Duration.Companion.seconds
-import java.util.UUID
-import javax.inject.Inject
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 @SysUISingleton
 class FromLockscreenTransitionInteractor
@@ -68,6 +71,8 @@
     powerInteractor: PowerInteractor,
     private val glanceableHubTransitions: GlanceableHubTransitions,
     private val communalSettingsInteractor: CommunalSettingsInteractor,
+    private val communalInteractor: CommunalInteractor,
+    private val communalSceneInteractor: CommunalSceneInteractor,
     private val swipeToDismissInteractor: SwipeToDismissInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
 ) :
@@ -94,6 +99,9 @@
         if (!communalSceneKtfRefactor()) {
             listenForLockscreenToGlanceableHub()
         }
+        if (communalSettingsInteractor.isV2FlagEnabled()) {
+            listenForLockscreenToGlanceableHubV2()
+        }
     }
 
     /**
@@ -268,9 +276,7 @@
                     it.transitionState == TransitionState.CANCELED &&
                         it.to == KeyguardState.PRIMARY_BOUNCER
                 }
-                .collect {
-                    transitionId = null
-                }
+                .collect { transitionId = null }
         }
     }
 
@@ -370,6 +376,19 @@
         }
     }
 
+    private fun listenForLockscreenToGlanceableHubV2() {
+        scope.launch {
+            communalInteractor.shouldShowCommunal
+                .filterRelevantKeyguardStateAnd { shouldShow -> shouldShow }
+                .collect {
+                    communalSceneInteractor.changeScene(
+                        newScene = CommunalScenes.Communal,
+                        loggingReason = "lockscreen to communal",
+                    )
+                }
+        }
+    }
+
     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
             interpolator = Interpolators.LINEAR
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
index 42cbd7d..a1f288e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
@@ -24,6 +24,8 @@
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.sample
@@ -32,6 +34,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
@@ -166,4 +169,14 @@
             isKeyguardEnabled.value && lockPatternUtils.isLockScreenDisabled(userId)
         }
     }
+
+    suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) {
+        isKeyguardEnabled
+            .logDiffsForTable(
+                tableLogBuffer = tableLogBuffer,
+                columnName = "isKeyguardEnabled",
+                initialValue = isKeyguardEnabled.value,
+            )
+            .collect()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 75178f0..3739d17 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -42,6 +42,8 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -60,6 +62,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.combineTransform
 import kotlinx.coroutines.flow.debounce
@@ -533,6 +536,16 @@
         repository.setNotificationStackAbsoluteBottom(bottom)
     }
 
+    suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) {
+        isDozing
+            .logDiffsForTable(
+                tableLogBuffer = tableLogBuffer,
+                columnName = "isDozing",
+                initialValue = isDozing.value,
+            )
+            .collect()
+    }
+
     companion object {
         private const val TAG = "KeyguardInteractor"
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index de5088c..898b68d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -475,7 +475,7 @@
             KeyguardPickerFlag(
                 name = Contract.FlagsTable.FLAG_NAME_CUSTOM_CLOCKS_ENABLED,
                 value =
-                    com.android.systemui.Flags.lockscreenCustomClocks() ||
+                    com.android.systemui.shared.Flags.lockscreenCustomClocks() ||
                         featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
             ),
             KeyguardPickerFlag(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 70a52af..017fe16 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -56,6 +56,7 @@
 import com.android.systemui.keyguard.domain.interactor.WallpaperFocalAreaInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.ui.view.layout.sections.AodPromotedNotificationSection
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
@@ -184,6 +185,7 @@
                         viewModel.translationY.collect { y ->
                             childViews[burnInLayerId]?.translationY = y
                             childViews[largeClockId]?.translationY = y
+                            childViews[aodPromotedNotificationId]?.translationY = y
                             childViews[aodNotificationIconContainerId]?.translationY = y
                         }
                     }
@@ -195,6 +197,7 @@
                                 state.isToOrFrom(KeyguardState.AOD) -> {
                                     // Large Clock is not translated in the x direction
                                     childViews[burnInLayerId]?.translationX = px
+                                    childViews[aodPromotedNotificationId]?.translationX = px
                                     childViews[aodNotificationIconContainerId]?.translationX = px
                                 }
                                 state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> {
@@ -291,11 +294,17 @@
                                 blueprintViewModel.refreshBlueprint()
                             }
                             childViews[aodNotificationIconContainerId]
-                                ?.setAodNotifIconContainerIsVisible(
-                                    isVisible,
-                                    iconsAppearTranslationPx.value,
-                                    screenOffAnimationController,
-                                )
+                                ?.setAodNotifIconContainerIsVisible(isVisible)
+                        }
+                    }
+
+                    launch {
+                        viewModel.isNotifIconContainerVisible.collect { isVisible ->
+                            if (isVisible.value) {
+                                blueprintViewModel.refreshBlueprint()
+                            }
+                            childViews[aodPromotedNotificationId]
+                                ?.setAodNotifIconContainerIsVisible(isVisible)
                         }
                     }
 
@@ -524,11 +533,7 @@
         }
     }
 
-    private fun View.setAodNotifIconContainerIsVisible(
-        isVisible: AnimatedValue<Boolean>,
-        iconsAppearTranslationPx: Int,
-        screenOffAnimationController: ScreenOffAnimationController,
-    ) {
+    private fun View.setAodNotifIconContainerIsVisible(isVisible: AnimatedValue<Boolean>) {
         animate().cancel()
         val animatorListener =
             object : AnimatorListenerAdapter() {
@@ -563,6 +568,7 @@
     }
 
     private val burnInLayerId = R.id.burn_in_layer
+    private val aodPromotedNotificationId = AodPromotedNotificationSection.viewId
     private val aodNotificationIconContainerId = R.id.aod_notification_icon_container
     private val largeClockId = customR.id.lockscreen_clock_view_large
     private val smallClockId = customR.id.lockscreen_clock_view
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt
index 454ba9a..d280862 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt
@@ -19,6 +19,7 @@
 package com.android.systemui.keyguard.ui.view
 
 import android.graphics.Rect
+import android.os.DeadObjectException
 import android.util.Log
 import android.view.View
 import com.android.systemui.dagger.SysUISingleton
@@ -192,7 +193,12 @@
 
         launcherAnimationController?.let {
             manualUnlockAmount = amount
-            it.setUnlockAmount(amount, forceIfAnimating)
+
+            try {
+                it.setUnlockAmount(amount, forceIfAnimating)
+            } catch (e: DeadObjectException) {
+                Log.e(TAG, "DeadObjectException in setUnlockAmount($amount, $forceIfAnimating)", e)
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index f0c924f..11a509a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -307,6 +307,16 @@
             BurnInScaleViewModel(scale = it.scale, scaleClockOnly = it.scaleClockOnly)
         }
 
+    val isAodPromotedNotifVisible: StateFlow<Boolean> =
+        keyguardTransitionInteractor
+            .transitionValue(AOD)
+            .map { it == 1f }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
     /** Is the notification icon container visible? */
     val isNotifIconContainerVisible: StateFlow<AnimatedValue<Boolean>> =
         combine(
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index ebda376..babb640 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -284,7 +284,10 @@
         }
 
         @Override
-        public void onDisplayReady(int displayId) {
+        public void onDisplayAddSystemDecorations(int displayId) {
+            if (enableDisplayContentModeManagement()) {
+                mHasNavBar.put(displayId, true);
+            }
             Display display = mDisplayManager.getDisplay(displayId);
             mIsLargeScreen = isLargeScreen(mContext);
             createNavigationBar(display, null /* savedState */, null /* result */);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 9d89430..c4d847f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -238,16 +238,16 @@
     }
 
     @Override
-    public void onDisplayReady(int displayId) {
-        CommandQueue.Callbacks.super.onDisplayReady(displayId);
+    public void onDisplayAddSystemDecorations(int displayId) {
+        CommandQueue.Callbacks.super.onDisplayAddSystemDecorations(displayId);
         if (mLauncherProxyService.getProxy() == null) {
             return;
         }
 
         try {
-            mLauncherProxyService.getProxy().onDisplayReady(displayId);
+            mLauncherProxyService.getProxy().onDisplayAddSystemDecorations(displayId);
         } catch (RemoteException e) {
-            Log.e(TAG, "onDisplayReady() failed", e);
+            Log.e(TAG, "onDisplayAddSystemDecorations() failed", e);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index f15a7b3..f8d442d 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorActual
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.data.repository.PowerRepository
 import com.android.systemui.power.shared.model.DozeScreenStateModel
@@ -35,6 +37,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 
@@ -228,6 +231,15 @@
         repository.updateWakefulness(powerButtonLaunchGestureTriggered = true)
     }
 
+    suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) {
+        detailedWakefulness
+            .logDiffsForTable(
+                tableLogBuffer = tableLogBuffer,
+                initialValue = detailedWakefulness.value,
+            )
+            .collect()
+    }
+
     companion object {
         private const val FSI_WAKE_WHY = "full_screen_intent"
 
diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
index 0f49c94..297c6af 100644
--- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
@@ -1,6 +1,8 @@
 package com.android.systemui.power.shared.model
 
 import com.android.systemui.keyguard.KeyguardService
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
 
 /**
  * Models whether the device is awake or asleep, along with information about why we're in that
@@ -35,7 +37,7 @@
      * to a subsequent power gesture.
      */
     val powerButtonLaunchGestureTriggered: Boolean = false,
-) {
+) : Diffable<WakefulnessModel> {
     fun isAwake() =
         internalWakefulnessState == WakefulnessState.AWAKE ||
             internalWakefulnessState == WakefulnessState.STARTING_TO_WAKE
@@ -58,4 +60,8 @@
         return isAwake() &&
             (lastWakeReason == WakeSleepReason.TAP || lastWakeReason == WakeSleepReason.GESTURE)
     }
+
+    override fun logDiffs(prevVal: WakefulnessModel, row: TableRowLogger) {
+        row.logChange(columnName = "wakefulness", value = toString())
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 07de466..85b677b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -32,11 +32,7 @@
 import androidx.activity.OnBackPressedDispatcherOwner
 import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
 import androidx.annotation.VisibleForTesting
-import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.core.tween
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.animation.togetherWith
 import androidx.compose.foundation.ScrollState
 import androidx.compose.foundation.layout.Arrangement.spacedBy
 import androidx.compose.foundation.layout.Box
@@ -120,6 +116,7 @@
 import com.android.systemui.qs.composefragment.ui.NotificationScrimClipParams
 import com.android.systemui.qs.composefragment.ui.notificationScrimClip
 import com.android.systemui.qs.composefragment.ui.quickQuickSettingsToQuickSettings
+import com.android.systemui.qs.composefragment.ui.toEditMode
 import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel
 import com.android.systemui.qs.flags.QSComposeFragment
 import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -144,6 +141,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
 
@@ -273,36 +271,7 @@
                                 // by the composables.
                                 .gesturesDisabled(viewModel.showingMirror)
                     ) {
-                        val isEditing by
-                            viewModel.containerViewModel.editModeViewModel.isEditing
-                                .collectAsStateWithLifecycle()
-                        val animationSpecEditMode = tween<Float>(EDIT_MODE_TIME_MILLIS)
-                        AnimatedContent(
-                            targetState = isEditing,
-                            transitionSpec = {
-                                fadeIn(animationSpecEditMode) togetherWith
-                                    fadeOut(animationSpecEditMode)
-                            },
-                            label = "EditModeAnimatedContent",
-                        ) { editing ->
-                            if (editing) {
-                                val qqsPadding = viewModel.qqsHeaderHeight
-                                EditMode(
-                                    viewModel = viewModel.containerViewModel.editModeViewModel,
-                                    modifier =
-                                        Modifier.fillMaxWidth()
-                                            .padding(top = { qqsPadding })
-                                            .padding(
-                                                horizontal = {
-                                                    QuickSettingsShade.Dimensions.Padding
-                                                        .roundToPx()
-                                                }
-                                            ),
-                                )
-                            } else {
-                                CollapsableQuickSettingsSTL()
-                            }
-                        }
+                        CollapsableQuickSettingsSTL()
                     }
                 }
             }
@@ -324,12 +293,17 @@
                         from(QuickQuickSettings, QuickSettings) {
                             quickQuickSettingsToQuickSettings(viewModel::animateTilesExpansion::get)
                         }
+                        to(SceneKeys.EditMode) {
+                            spec = tween(durationMillis = EDIT_MODE_TIME_MILLIS)
+                            toEditMode()
+                        }
                     },
             )
 
         LaunchedEffect(Unit) {
             synchronizeQsState(
                 sceneState,
+                viewModel.containerViewModel.editModeViewModel.isEditing,
                 snapshotFlow { viewModel.expansionState }.map { it.progress },
             )
         }
@@ -337,12 +311,20 @@
         SceneTransitionLayout(state = sceneState, modifier = Modifier.fillMaxSize()) {
             scene(QuickSettings) {
                 LaunchedEffect(Unit) { viewModel.onQSOpen() }
-                QuickSettingsElement()
+                QuickSettingsElement(Modifier.element(QuickSettings.rootElementKey))
             }
 
             scene(QuickQuickSettings) {
                 LaunchedEffect(Unit) { viewModel.onQQSOpen() }
-                QuickQuickSettingsElement()
+                // Cannot pass the element modifier in because the top element has a `testTag`
+                // and this would overwrite it.
+                Box(Modifier.element(QuickQuickSettings.rootElementKey)) {
+                    QuickQuickSettingsElement()
+                }
+            }
+
+            scene(SceneKeys.EditMode) {
+                EditModeElement(Modifier.element(SceneKeys.EditMode.rootElementKey))
             }
         }
     }
@@ -582,7 +564,7 @@
     }
 
     @Composable
-    private fun ContentScope.QuickQuickSettingsElement() {
+    private fun ContentScope.QuickQuickSettingsElement(modifier: Modifier = Modifier) {
         val qqsPadding = viewModel.qqsHeaderHeight
         val bottomPadding = viewModel.qqsBottomPadding
         DisposableEffect(Unit) {
@@ -595,7 +577,7 @@
                 .squishiness
                 .collectAsStateWithLifecycle()
 
-        Column(modifier = Modifier.sysuiResTag(ResIdTags.quickQsPanel)) {
+        Column(modifier = modifier.sysuiResTag(ResIdTags.quickQsPanel)) {
             Box(
                 modifier =
                     Modifier.fillMaxWidth()
@@ -666,12 +648,12 @@
     }
 
     @Composable
-    private fun ContentScope.QuickSettingsElement() {
+    private fun ContentScope.QuickSettingsElement(modifier: Modifier = Modifier) {
         val qqsPadding = viewModel.qqsHeaderHeight
         val qsExtraPadding = dimensionResource(R.dimen.qs_panel_padding_top)
         Column(
             modifier =
-                Modifier.collapseExpandSemanticAction(
+                modifier.collapseExpandSemanticAction(
                     stringResource(id = R.string.accessibility_quick_settings_collapse)
                 )
         ) {
@@ -776,6 +758,18 @@
         }
     }
 
+    @Composable
+    private fun EditModeElement(modifier: Modifier = Modifier) {
+        // No need for top padding, the Scaffold inside takes care of the correct insets
+        EditMode(
+            viewModel = viewModel.containerViewModel.editModeViewModel,
+            modifier =
+                modifier
+                    .fillMaxWidth()
+                    .padding(horizontal = { QuickSettingsShade.Dimensions.Padding.roundToPx() }),
+        )
+    }
+
     private fun Modifier.collapseExpandSemanticAction(label: String): Modifier {
         return viewModel.collapseExpandAccessibilityAction?.let {
             semantics {
@@ -863,6 +857,7 @@
 object SceneKeys {
     val QuickQuickSettings = SceneKey("QuickQuickSettingsScene")
     val QuickSettings = SceneKey("QuickSettingsScene")
+    val EditMode = SceneKey("EditModeScene")
 
     fun QSFragmentComposeViewModel.QSExpansionState.toIdleSceneKey(): SceneKey {
         return when {
@@ -880,7 +875,11 @@
         }
 }
 
-suspend fun synchronizeQsState(state: MutableSceneTransitionLayoutState, expansion: Flow<Float>) {
+private suspend fun synchronizeQsState(
+    state: MutableSceneTransitionLayoutState,
+    editMode: Flow<Boolean>,
+    expansion: Flow<Float>,
+) {
     coroutineScope {
         val animationScope = this
 
@@ -891,23 +890,30 @@
             currentTransition = null
         }
 
-        expansion.collectLatest { progress ->
-            when (progress) {
-                0f -> snapTo(QuickQuickSettings)
-                1f -> snapTo(QuickSettings)
-                else -> {
-                    val transition = currentTransition
-                    if (transition != null) {
-                        transition.progress = progress
-                        return@collectLatest
-                    }
+        editMode.combine(expansion, ::Pair).collectLatest { (editMode, progress) ->
+            if (editMode && state.currentScene != SceneKeys.EditMode) {
+                state.setTargetScene(SceneKeys.EditMode, animationScope)?.second?.join()
+            } else if (!editMode && state.currentScene == SceneKeys.EditMode) {
+                state.setTargetScene(SceneKeys.QuickSettings, animationScope)?.second?.join()
+            }
+            if (!editMode) {
+                when (progress) {
+                    0f -> snapTo(QuickQuickSettings)
+                    1f -> snapTo(QuickSettings)
+                    else -> {
+                        val transition = currentTransition
+                        if (transition != null) {
+                            transition.progress = progress
+                            return@collectLatest
+                        }
 
-                    val newTransition =
-                        ExpansionTransition(progress).also { currentTransition = it }
-                    state.startTransitionImmediately(
-                        animationScope = animationScope,
-                        transition = newTransition,
-                    )
+                        val newTransition =
+                            ExpansionTransition(progress).also { currentTransition = it }
+                        state.startTransitionImmediately(
+                            animationScope = animationScope,
+                            transition = newTransition,
+                        )
+                    }
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/ToEditMode.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/ToEditMode.kt
new file mode 100644
index 0000000..0c6f3ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/ToEditMode.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2025 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.ui
+
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.qs.composefragment.SceneKeys
+
+fun TransitionBuilder.toEditMode() {
+    fractionRange(start = 0.5f) { fade(SceneKeys.EditMode.rootElementKey) }
+    fractionRange(end = 0.5f) {
+        fade(SceneKeys.QuickQuickSettings.rootElementKey)
+        fade(SceneKeys.QuickSettings.rootElementKey)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt
index be792df..f2f237a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt
@@ -16,13 +16,27 @@
 
 package com.android.systemui.scene.domain
 
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.scene.domain.resolver.SceneResolverModule
 import dagger.Module
+import dagger.Provides
+import javax.inject.Qualifier
 
-@Module(
-    includes =
-        [
-            SceneResolverModule::class,
-        ]
-)
-object SceneDomainModule
+@Module(includes = [SceneResolverModule::class])
+object SceneDomainModule {
+
+    @JvmStatic
+    @Provides
+    @SysUISingleton
+    @SceneFrameworkTableLog
+    fun provideSceneFrameworkTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
+        return factory.create("SceneFrameworkTableLog", 100)
+    }
+}
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class SceneFrameworkTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
index bebd398..c9d8e02 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
@@ -18,11 +18,16 @@
 
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableRowLogger
 import com.android.systemui.scene.data.model.SceneStack
+import com.android.systemui.scene.data.model.asIterable
 import com.android.systemui.scene.data.model.peek
 import com.android.systemui.scene.data.model.pop
 import com.android.systemui.scene.data.model.push
 import com.android.systemui.scene.data.model.sceneStackOf
+import com.android.systemui.scene.domain.SceneFrameworkTableLog
 import com.android.systemui.scene.shared.logger.SceneLogger
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import javax.inject.Inject
@@ -39,6 +44,7 @@
 constructor(
     private val logger: SceneLogger,
     private val sceneContainerConfig: SceneContainerConfig,
+    @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer,
 ) {
     private val _backStack = MutableStateFlow(sceneStackOf())
     val backStack: StateFlow<SceneStack> = _backStack.asStateFlow()
@@ -58,6 +64,7 @@
     fun onSceneChange(from: SceneKey, to: SceneKey) {
         check(from != to) { "from == to, from=${from.debugName}, to=${to.debugName}" }
 
+        val prevVal = backStack.value
         _backStack.update { stack ->
             when (stackOperation(from, to, stack)) {
                 null -> stack
@@ -68,12 +75,21 @@
             }
         }
         logger.logSceneBackStack(backStack.value)
+        tableLogBuffer.logDiffs(
+            prevVal = DiffableSceneStack(prevVal),
+            newVal = DiffableSceneStack(backStack.value),
+        )
     }
 
     /** Applies the given [transform] to the back stack. */
     fun updateBackStack(transform: (SceneStack) -> SceneStack) {
+        val prevVal = backStack.value
         _backStack.update { stack -> transform(stack) }
         logger.logSceneBackStack(backStack.value)
+        tableLogBuffer.logDiffs(
+            prevVal = DiffableSceneStack(prevVal),
+            newVal = DiffableSceneStack(backStack.value),
+        )
     }
 
     private fun stackOperation(from: SceneKey, to: SceneKey, stack: SceneStack): StackOperation? {
@@ -106,4 +122,15 @@
     private data object Push : StackOperation
 
     private data object Pop : StackOperation
+
+    private class DiffableSceneStack(private val sceneStack: SceneStack) :
+        Diffable<DiffableSceneStack> {
+
+        override fun logDiffs(prevVal: DiffableSceneStack, row: TableRowLogger) {
+            row.logChange(
+                columnName = "backStack",
+                value = sceneStack.asIterable().joinToString { it.debugName },
+            )
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 8bc9d96..9c04323 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -27,14 +27,19 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableRowLogger
 import com.android.systemui.scene.data.repository.SceneContainerRepository
 import com.android.systemui.scene.domain.resolver.SceneResolver
 import com.android.systemui.scene.shared.logger.SceneLogger
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.pairwise
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -47,6 +52,7 @@
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
 
 /**
  * Generic business logic and app state accessors for the scene framework.
@@ -562,6 +568,28 @@
         decrementActiveTransitionAnimationCount()
     }
 
+    suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) {
+        coroutineScope {
+            launch {
+                currentScene
+                    .map { sceneKey -> DiffableSceneKey(key = sceneKey) }
+                    .pairwise()
+                    .collect { (prev, current) ->
+                        tableLogBuffer.logDiffs(prevVal = prev, newVal = current)
+                    }
+            }
+
+            launch {
+                currentOverlays
+                    .map { overlayKeys -> DiffableOverlayKeys(keys = overlayKeys) }
+                    .pairwise()
+                    .collect { (prev, current) ->
+                        tableLogBuffer.logDiffs(prevVal = prev, newVal = current)
+                    }
+            }
+        }
+    }
+
     private fun decrementActiveTransitionAnimationCount() {
         repository.activeTransitionAnimationCount.update { current ->
             (current - 1).also {
@@ -573,4 +601,20 @@
             }
         }
     }
+
+    private class DiffableSceneKey(private val key: SceneKey) : Diffable<DiffableSceneKey> {
+        override fun logDiffs(prevVal: DiffableSceneKey, row: TableRowLogger) {
+            row.logChange(columnName = "currentScene", value = key.debugName)
+        }
+    }
+
+    private class DiffableOverlayKeys(private val keys: Set<OverlayKey>) :
+        Diffable<DiffableOverlayKeys> {
+        override fun logDiffs(prevVal: DiffableOverlayKeys, row: TableRowLogger) {
+            row.logChange(
+                columnName = "currentOverlays",
+                value = keys.joinToString { key -> key.debugName },
+            )
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 8602884..2fd5841 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -45,6 +45,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor.Companion.keyguardScenes
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.model.SceneContainerPlugin
 import com.android.systemui.model.SysUiState
 import com.android.systemui.model.updateFlags
@@ -54,6 +55,7 @@
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.scene.data.model.asIterable
 import com.android.systemui.scene.data.model.sceneStackOf
+import com.android.systemui.scene.domain.SceneFrameworkTableLog
 import com.android.systemui.scene.domain.interactor.DisabledContentInteractor
 import com.android.systemui.scene.domain.interactor.SceneBackInteractor
 import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
@@ -145,6 +147,7 @@
     private val disabledContentInteractor: DisabledContentInteractor,
     private val activityTransitionAnimator: ActivityTransitionAnimator,
     private val shadeModeInteractor: ShadeModeInteractor,
+    @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer,
 ) : CoreStartable {
     private val centralSurfaces: CentralSurfaces?
         get() = centralSurfacesOptLazy.get().getOrNull()
@@ -154,6 +157,7 @@
     override fun start() {
         if (SceneContainerFlag.isEnabled) {
             sceneLogger.logFrameworkEnabled(isEnabled = true)
+            applicationScope.launch { hydrateTableLogBuffer() }
             hydrateVisibility()
             automaticallySwitchScenes()
             hydrateSystemUiState()
@@ -224,6 +228,16 @@
         }
     }
 
+    private suspend fun hydrateTableLogBuffer() {
+        coroutineScope {
+            launch { sceneInteractor.hydrateTableLogBuffer(tableLogBuffer) }
+            launch { keyguardEnabledInteractor.hydrateTableLogBuffer(tableLogBuffer) }
+            launch { faceUnlockInteractor.hydrateTableLogBuffer(tableLogBuffer) }
+            launch { powerInteractor.hydrateTableLogBuffer(tableLogBuffer) }
+            launch { keyguardInteractor.hydrateTableLogBuffer(tableLogBuffer) }
+        }
+    }
+
     private fun resetShadeSessions() {
         applicationScope.launch {
             combine(
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 6844f05..eae0ba6 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.settings.brightness;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.content.Intent.EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -157,6 +159,7 @@
     }
 
     void setBrightnessDialogViewAttributes(View container) {
+        Configuration configuration = getResources().getConfiguration();
         // The brightness mirror container is INVISIBLE by default.
         container.setVisibility(View.VISIBLE);
         ViewGroup.MarginLayoutParams lp =
@@ -171,9 +174,16 @@
                         R.dimen.notification_guts_option_vertical_padding);
 
         lp.topMargin = verticalMargin;
+        // If in multi-window or freeform, increase the top margin so the brightness dialog
+        // doesn't get cut off.
+        final int windowingMode = configuration.windowConfiguration.getWindowingMode();
+        if (windowingMode == WINDOWING_MODE_MULTI_WINDOW
+                || windowingMode == WINDOWING_MODE_FREEFORM) {
+            lp.topMargin += 50;
+        }
+
         lp.bottomMargin = verticalMargin;
 
-        Configuration configuration = getResources().getConfiguration();
         int orientation = configuration.orientation;
         int windowWidth = getWindowAvailableWidth();
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
index 59d8124..0145150 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
@@ -19,6 +19,9 @@
 import android.provider.Settings
 import androidx.annotation.FloatRange
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.scene.domain.SceneFrameworkTableLog
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.shade.shared.model.ShadeMode
@@ -81,8 +84,9 @@
 @Inject
 constructor(
     @Application applicationScope: CoroutineScope,
-    repository: ShadeRepository,
+    private val repository: ShadeRepository,
     secureSettingsRepository: SecureSettingsRepository,
+    @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer,
 ) : ShadeModeInteractor {
 
     private val isDualShadeEnabled: Flow<Boolean> =
@@ -93,17 +97,17 @@
 
     override val isShadeLayoutWide: StateFlow<Boolean> = repository.isShadeLayoutWide
 
+    private val shadeModeInitialValue: ShadeMode
+        get() =
+            determineShadeMode(
+                isDualShadeEnabled = DUAL_SHADE_ENABLED_DEFAULT,
+                isShadeLayoutWide = repository.isShadeLayoutWide.value,
+            )
+
     override val shadeMode: StateFlow<ShadeMode> =
         combine(isDualShadeEnabled, repository.isShadeLayoutWide, ::determineShadeMode)
-            .stateIn(
-                applicationScope,
-                SharingStarted.Eagerly,
-                initialValue =
-                    determineShadeMode(
-                        isDualShadeEnabled = DUAL_SHADE_ENABLED_DEFAULT,
-                        isShadeLayoutWide = repository.isShadeLayoutWide.value,
-                    ),
-            )
+            .logDiffsForTable(tableLogBuffer = tableLogBuffer, initialValue = shadeModeInitialValue)
+            .stateIn(applicationScope, SharingStarted.Eagerly, initialValue = shadeModeInitialValue)
 
     @FloatRange(from = 0.0, to = 1.0) override fun getTopEdgeSplitFraction(): Float = 0.5f
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
index a8199a4..8b3ce0f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
@@ -16,15 +16,18 @@
 
 package com.android.systemui.shade.shared.model
 
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
 /** Enumerates all known modes of operation of the shade. */
-sealed interface ShadeMode {
+sealed class ShadeMode : Diffable<ShadeMode> {
 
     /**
      * The single or "accordion" shade where the QS and notification parts are in two vertically
      * stacked panels and the user can swipe up and down to expand or collapse between the two
      * parts.
      */
-    data object Single : ShadeMode
+    data object Single : ShadeMode()
 
     /**
      * The split shade where, on large screens and unfolded foldables, the QS and notification parts
@@ -32,14 +35,18 @@
      *
      * Note: This isn't the only mode where the shade is wide.
      */
-    data object Split : ShadeMode
+    data object Split : ShadeMode()
 
     /**
      * The dual shade where the QS and notification parts each have their own independently
      * expandable/collapsible panel on either side of the large screen / unfolded device or sharing
      * a space on a small screen or folded device.
      */
-    data object Dual : ShadeMode
+    data object Dual : ShadeMode()
+
+    override fun logDiffs(prevVal: ShadeMode, row: TableRowLogger) {
+        row.logChange("shadeMode", toString())
+    }
 
     companion object {
         @JvmStatic fun dual(): Dual = Dual
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index dcea8d8..1720898 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -111,7 +111,7 @@
     private static final int MSG_COLLAPSE_PANELS                   = 4 << MSG_SHIFT;
     private static final int MSG_EXPAND_SETTINGS                   = 5 << MSG_SHIFT;
     private static final int MSG_SYSTEM_BAR_CHANGED                = 6 << MSG_SHIFT;
-    private static final int MSG_DISPLAY_READY                     = 7 << MSG_SHIFT;
+    private static final int MSG_DISPLAY_ADD_SYSTEM_DECORATIONS    = 7 << MSG_SHIFT;
     private static final int MSG_SHOW_IME_BUTTON                   = 8 << MSG_SHIFT;
     private static final int MSG_TOGGLE_RECENT_APPS                = 9 << MSG_SHIFT;
     private static final int MSG_PRELOAD_RECENT_APPS               = 10 << MSG_SHIFT;
@@ -415,9 +415,9 @@
         }
 
         /**
-         * @see IStatusBar#onDisplayReady(int)
+         * @see IStatusBar#onDisplayAddSystemDecorations(int)
          */
-        default void onDisplayReady(int displayId) {
+        default void onDisplayAddSystemDecorations(int displayId) {
         }
 
         /**
@@ -1205,9 +1205,9 @@
     }
 
     @Override
-    public void onDisplayReady(int displayId) {
+    public void onDisplayAddSystemDecorations(int displayId) {
         synchronized (mLock) {
-            mHandler.obtainMessage(MSG_DISPLAY_READY, displayId, 0).sendToTarget();
+            mHandler.obtainMessage(MSG_DISPLAY_ADD_SYSTEM_DECORATIONS, displayId, 0).sendToTarget();
         }
     }
 
@@ -1851,9 +1851,9 @@
                         mCallbacks.get(i).showPinningEscapeToast();
                     }
                     break;
-                case MSG_DISPLAY_READY:
+                case MSG_DISPLAY_ADD_SYSTEM_DECORATIONS:
                     for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).onDisplayReady(msg.arg1);
+                        mCallbacks.get(i).onDisplayAddSystemDecorations(msg.arg1);
                     }
                     break;
                 case MSG_DISPLAY_REMOVE_SYSTEM_DECORATIONS:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index d46638f..f5764d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -418,6 +418,7 @@
             }
             is OngoingActivityChipModel.Shown.Timer,
             is OngoingActivityChipModel.Shown.Text,
+            is OngoingActivityChipModel.Shown.ShortTimeDelta,
             is OngoingActivityChipModel.Shown.IconOnly -> {
                 chipView.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
index 13f4e51..375e029 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
@@ -95,6 +95,10 @@
         is OngoingActivityChipModel.Shown.ShortTimeDelta -> {
             // TODO(b/372657935): Implement ShortTimeDelta content in compose.
         }
+
+        is OngoingActivityChipModel.Shown.IconOnly -> {
+            throw IllegalStateException("ChipContent should only be used if the chip shows text")
+        }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index c6d6da2..e0c7645 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -40,7 +40,7 @@
     }
 
     /** This chip should be shown with the given information. */
-    abstract class Shown(
+    sealed class Shown(
         /** The icon to show on the chip. If null, no icon will be shown. */
         open val icon: ChipIcon?,
         /** What colors to use for the chip. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
index 2d1eccd..a0a8671 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
@@ -22,6 +22,7 @@
 import com.android.internal.widget.MessagingMessage
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.Flags
 import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener
@@ -144,7 +145,12 @@
         )
         log { "ViewConfigCoordinator.updateNotificationsOnUiModeChanged()" }
         traceSection("updateNotifOnUiModeChanged") {
-            mPipeline?.allNotifs?.forEach { entry -> entry.row?.onUiModeChanged() }
+            mPipeline?.allNotifs?.forEach { entry ->
+                entry.row?.onUiModeChanged()
+                if (Flags.notificationUndoGutsOnConfigChanged()) {
+                    mGutsManager.closeAndUndoGuts()
+                }
+            }
         }
     }
 
@@ -152,9 +158,15 @@
         colorUpdateLogger.logEvent("VCC.updateNotificationsOnDensityOrFontScaleChanged()")
         mPipeline?.allNotifs?.forEach { entry ->
             entry.onDensityOrFontScaleChanged()
-            val exposedGuts = entry.areGutsExposed()
-            if (exposedGuts) {
-                mGutsManager.onDensityOrFontScaleChanged(entry)
+            if (Flags.notificationUndoGutsOnConfigChanged()) {
+                mGutsManager.closeAndUndoGuts()
+            } else {
+                // This property actually gets reset when the guts are re-inflated, so we're never
+                // actually calling onDensityOrFontScaleChanged below.
+                val exposedGuts = entry.areGutsExposed()
+                if (exposedGuts) {
+                    mGutsManager.onDensityOrFontScaleChanged(entry)
+                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
index cb9bd4a..33c71d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
@@ -27,8 +27,19 @@
 import android.widget.ImageView
 import android.widget.ProgressBar
 import android.widget.TextView
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.key
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.core.view.isVisible
 import com.android.app.tracing.traceSection
@@ -41,6 +52,8 @@
 import com.android.internal.widget.NotificationRowIconView
 import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.res.R as systemuiR
+import com.android.systemui.statusbar.notification.promoted.AodPromotedNotificationColor.PrimaryText
+import com.android.systemui.statusbar.notification.promoted.AodPromotedNotificationColor.SecondaryText
 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style
 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When
@@ -59,31 +72,55 @@
     key(content.identity) {
         val layoutResource = content.layoutResource ?: return
 
-        AndroidView(
-            factory = { context ->
-                traceSection("$TAG.inflate") {
-                        LayoutInflater.from(context).inflate(layoutResource, /* root= */ null)
-                    }
-                    .apply {
-                        setTag(
-                            viewUpdaterTagId,
-                            traceSection("$TAG.findViews") {
-                                AODPromotedNotificationViewUpdater(this)
-                            },
-                        )
-                    }
-            },
-            update = { view ->
-                traceSection("$TAG.update") {
-                    (view.getTag(viewUpdaterTagId) as AODPromotedNotificationViewUpdater).update(
-                        content
-                    )
-                }
-            },
-        )
+        val topPadding = dimensionResource(systemuiR.dimen.below_clock_padding_start_icons)
+        val sidePaddings = dimensionResource(systemuiR.dimen.notification_side_paddings)
+        val paddingValues =
+            PaddingValues(top = topPadding, start = sidePaddings, end = sidePaddings, bottom = 0.dp)
+
+        val borderStroke = BorderStroke(1.dp, SecondaryText.brush)
+
+        val borderRadius = dimensionResource(systemuiR.dimen.notification_corner_radius)
+        val borderShape = RoundedCornerShape(borderRadius)
+
+        Box(modifier = Modifier.padding(paddingValues)) {
+            AODPromotedNotificationView(
+                layoutResource = layoutResource,
+                content = content,
+                modifier = Modifier.border(borderStroke, borderShape),
+            )
+        }
     }
 }
 
+@Composable
+fun AODPromotedNotificationView(
+    layoutResource: Int,
+    content: PromotedNotificationContentModel,
+    modifier: Modifier = Modifier,
+) {
+    AndroidView(
+        factory = { context ->
+            val view =
+                traceSection("$TAG.inflate") {
+                    LayoutInflater.from(context).inflate(layoutResource, /* root= */ null)
+                }
+
+            val updater =
+                traceSection("$TAG.findViews") { AODPromotedNotificationViewUpdater(view) }
+
+            view.setTag(viewUpdaterTagId, updater)
+
+            view
+        },
+        update = { view ->
+            val updater = view.getTag(viewUpdaterTagId) as AODPromotedNotificationViewUpdater
+
+            traceSection("$TAG.update") { updater.update(content) }
+        },
+        modifier = modifier,
+    )
+}
+
 private val PromotedNotificationContentModel.layoutResource: Int?
     get() {
         return if (Flags.notificationsRedesignTemplates()) {
@@ -262,12 +299,12 @@
     }
 
     private fun updateTitle(titleView: TextView?, content: PromotedNotificationContentModel) {
-        updateTextView(titleView, content.title, color = Color.PrimaryText)
+        updateTextView(titleView, content.title, color = PrimaryText)
     }
 
     private fun updateTimeAndChronometer(content: PromotedNotificationContentModel) {
-        setTextViewColor(time, Color.SecondaryText)
-        setTextViewColor(chronometer, Color.SecondaryText)
+        setTextViewColor(time, SecondaryText)
+        setTextViewColor(chronometer, SecondaryText)
 
         val timeValue = content.time
 
@@ -309,7 +346,7 @@
     private fun updateTextView(
         view: TextView?,
         text: CharSequence?,
-        color: Color = Color.SecondaryText,
+        color: AodPromotedNotificationColor = SecondaryText,
     ) {
         setTextViewColor(view, color)
 
@@ -322,15 +359,19 @@
         }
     }
 
-    private fun setTextViewColor(view: TextView?, color: Color) {
-        view?.setTextColor(color.color.toInt())
+    private fun setTextViewColor(view: TextView?, color: AodPromotedNotificationColor) {
+        view?.setTextColor(color.colorInt)
     }
+}
 
-    private enum class Color(val color: UInt) {
-        Background(0x00000000u),
-        PrimaryText(0xFFFFFFFFu),
-        SecondaryText(0xFFCCCCCCu),
-    }
+private enum class AodPromotedNotificationColor(colorUInt: UInt) {
+    Background(0x00000000u),
+    PrimaryText(0xFFFFFFFFu),
+    SecondaryText(0xFFCCCCCCu);
+
+    val colorInt = colorUInt.toInt()
+    val color = Color(colorInt)
+    val brush = SolidColor(color)
 }
 
 private val viewUpdaterTagId = systemuiR.id.aod_promoted_notification_view_updater_tag
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
index b86d1d9..75d1c7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
@@ -287,7 +287,7 @@
      * @param save whether the state should be saved
      * @param force whether the guts should be force-closed regardless of state.
      */
-    private void closeControls(int x, int y, boolean save, boolean force) {
+    public void closeControls(int x, int y, boolean save, boolean force) {
         // First try to dismiss any blocking helper.
         if (getWindowToken() == null) {
             if (mClosedListener != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index b1e5b22..445cd01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -48,6 +48,7 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.settingslib.notification.ConversationIconFactory;
 import com.android.systemui.CoreStartable;
+import com.android.systemui.Flags;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -223,6 +224,10 @@
     }
 
     public void onDensityOrFontScaleChanged(NotificationEntry entry) {
+        if (!Flags.notificationUndoGutsOnConfigChanged()) {
+            Log.wtf(TAG, "onDensityOrFontScaleChanged should not be called if"
+                    + " notificationUndoGutsOnConfigChanged is off");
+        }
         setExposedGuts(entry.getGuts());
         bindGuts(entry.getRow());
     }
@@ -590,7 +595,8 @@
     }
 
     /**
-     * Closes guts or notification menus that might be visible and saves any changes.
+     * Closes guts or notification menus that might be visible and saves any changes if applicable
+     * (see {@link NotificationGuts.GutsContent#shouldBeSavedOnClose}).
      *
      * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
      * @param force true if guts should be closed regardless of state (used for snooze only).
@@ -611,6 +617,20 @@
     }
 
     /**
+     * Closes all guts that might be visible without saving changes.
+     */
+    public void closeAndUndoGuts() {
+        if (mNotificationGutsExposed != null) {
+            mNotificationGutsExposed.removeCallbacks(mOpenRunnable);
+            mNotificationGutsExposed.closeControls(
+                    /* x = */ -1,
+                    /* y = */ -1,
+                    /* save = */ false,
+                    /* force = */ false);
+        }
+    }
+
+    /**
      * Returns the exposed NotificationGuts or null if none are exposed.
      */
     public NotificationGuts getExposedGuts() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index 99a6f6a..83897f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -51,6 +51,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Flags;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 import com.android.systemui.res.R;
@@ -86,18 +87,26 @@
     private NotificationSwipeActionHelper mSnoozeListener;
     private StatusBarNotification mSbn;
 
-    private View mSnoozeView;
-    private TextView mSelectedOptionText;
+    @VisibleForTesting
+    public View mSnoozeView;
+    @VisibleForTesting
+    public TextView mSelectedOptionText;
     private TextView mUndoButton;
-    private ImageView mExpandButton;
-    private View mDivider;
-    private ViewGroup mSnoozeOptionContainer;
-    private List<SnoozeOption> mSnoozeOptions;
+    @VisibleForTesting
+    public ImageView mExpandButton;
+    @VisibleForTesting
+    public View mDivider;
+    @VisibleForTesting
+    public ViewGroup mSnoozeOptionContainer;
+    @VisibleForTesting
+    public List<SnoozeOption> mSnoozeOptions;
     private int mCollapsedHeight;
     private SnoozeOption mDefaultOption;
-    private SnoozeOption mSelectedOption;
+    @VisibleForTesting
+    public SnoozeOption mSelectedOption;
     private boolean mSnoozing;
-    private boolean mExpanded;
+    @VisibleForTesting
+    public boolean mExpanded;
     private AnimatorSet mExpandAnimation;
     private KeyValueListParser mParser;
 
@@ -334,7 +343,8 @@
         }
     }
 
-    private void showSnoozeOptions(boolean show) {
+    @VisibleForTesting
+    public void showSnoozeOptions(boolean show) {
         int drawableId = show ? com.android.internal.R.drawable.ic_collapse_notification
                 : com.android.internal.R.drawable.ic_expand_notification;
         mExpandButton.setImageResource(drawableId);
@@ -381,7 +391,8 @@
         mExpandAnimation.start();
     }
 
-    private void setSelected(SnoozeOption option, boolean userAction) {
+    @VisibleForTesting
+    public void setSelected(SnoozeOption option, boolean userAction) {
         if (option != mSelectedOption) {
             mSelectedOption = option;
             mSelectedOptionText.setText(option.getConfirmation());
@@ -466,7 +477,12 @@
 
     @Override
     public boolean handleCloseControls(boolean save, boolean force) {
-        if (mExpanded && !force) {
+        if (Flags.notificationUndoGutsOnConfigChanged() && !save) {
+            // Undo changes and let the guts handle closing the view
+            mSelectedOption = null;
+            showSnoozeOptions(false);
+            return false;
+        } else if (mExpanded && !force) {
             // Collapse expanded state on outside touch
             showSnoozeOptions(false);
             return true;
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
index 9795cda..eecea92 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
@@ -27,6 +27,7 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.safeDrawingPadding
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
@@ -82,18 +83,29 @@
                     }
                 ),
     ) {
-        val padding = if (hasCompactWindowSize()) 24.dp else 60.dp
+        val isCompactWindow = hasCompactWindowSize()
+        val padding = if (isCompactWindow) 24.dp else 60.dp
         val configuration = LocalConfiguration.current
         when (configuration.orientation) {
             Configuration.ORIENTATION_LANDSCAPE -> {
-                HorizontalSelectionButtons(
-                    onBackTutorialClicked = onBackTutorialClicked,
-                    onHomeTutorialClicked = onHomeTutorialClicked,
-                    onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
-                    onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked,
-                    modifier = Modifier.weight(1f).padding(padding),
-                    lastSelectedScreen,
-                )
+                if (isCompactWindow)
+                    HorizontalCompactSelectionButtons(
+                        onBackTutorialClicked = onBackTutorialClicked,
+                        onHomeTutorialClicked = onHomeTutorialClicked,
+                        onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
+                        onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked,
+                        lastSelectedScreen,
+                        modifier = Modifier.weight(1f).padding(padding),
+                    )
+                else
+                    HorizontalSelectionButtons(
+                        onBackTutorialClicked = onBackTutorialClicked,
+                        onHomeTutorialClicked = onHomeTutorialClicked,
+                        onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
+                        onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked,
+                        lastSelectedScreen,
+                        modifier = Modifier.weight(1f).padding(padding),
+                    )
             }
             else -> {
                 VerticalSelectionButtons(
@@ -101,8 +113,8 @@
                     onHomeTutorialClicked = onHomeTutorialClicked,
                     onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
                     onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked,
-                    modifier = Modifier.weight(1f).padding(padding),
                     lastSelectedScreen,
+                    modifier = Modifier.weight(1f).padding(padding),
                 )
             }
         }
@@ -120,11 +132,99 @@
     onHomeTutorialClicked: () -> Unit,
     onRecentAppsTutorialClicked: () -> Unit,
     onSwitchAppsTutorialClicked: () -> Unit,
-    modifier: Modifier = Modifier,
     lastSelectedScreen: Screen,
+    modifier: Modifier = Modifier,
+) {
+    Column(modifier = modifier) {
+        TwoByTwoTutorialButtons(
+            onBackTutorialClicked,
+            onHomeTutorialClicked,
+            onRecentAppsTutorialClicked,
+            onSwitchAppsTutorialClicked,
+            lastSelectedScreen,
+            modifier = Modifier.weight(1f).fillMaxSize(),
+        )
+    }
+}
+
+@Composable
+private fun TwoByTwoTutorialButtons(
+    onBackTutorialClicked: () -> Unit,
+    onHomeTutorialClicked: () -> Unit,
+    onRecentAppsTutorialClicked: () -> Unit,
+    onSwitchAppsTutorialClicked: () -> Unit,
+    lastSelectedScreen: Screen,
+    modifier: Modifier = Modifier,
+) {
+    val homeFocusRequester = remember { FocusRequester() }
+    val backFocusRequester = remember { FocusRequester() }
+    val recentAppsFocusRequester = remember { FocusRequester() }
+    val switchAppsFocusRequester = remember { FocusRequester() }
+    LaunchedEffect(Unit) {
+        when (lastSelectedScreen) {
+            Screen.HOME_GESTURE -> homeFocusRequester.requestFocus()
+            Screen.BACK_GESTURE -> backFocusRequester.requestFocus()
+            Screen.RECENT_APPS_GESTURE -> recentAppsFocusRequester.requestFocus()
+            Screen.SWITCH_APPS_GESTURE -> switchAppsFocusRequester.requestFocus()
+            else -> {} // No-Op.
+        }
+    }
+    Column {
+        Row(Modifier.weight(1f)) {
+            TutorialButton(
+                text = stringResource(R.string.touchpad_tutorial_home_gesture_button),
+                icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_home_icon),
+                iconColor = MaterialTheme.colorScheme.onPrimary,
+                onClick = onHomeTutorialClicked,
+                backgroundColor = MaterialTheme.colorScheme.primary,
+                modifier = modifier.focusRequester(homeFocusRequester).focusable().fillMaxSize(),
+            )
+            Spacer(modifier = Modifier.size(16.dp))
+            TutorialButton(
+                text = stringResource(R.string.touchpad_tutorial_back_gesture_button),
+                icon = Icons.AutoMirrored.Outlined.ArrowBack,
+                iconColor = MaterialTheme.colorScheme.onTertiary,
+                onClick = onBackTutorialClicked,
+                backgroundColor = MaterialTheme.colorScheme.tertiary,
+                modifier = modifier.focusRequester(backFocusRequester).focusable().fillMaxSize(),
+            )
+        }
+        Spacer(modifier = Modifier.size(16.dp))
+        Row(Modifier.weight(1f)) {
+            TutorialButton(
+                text = stringResource(R.string.touchpad_tutorial_recent_apps_gesture_button),
+                icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_recents_icon),
+                iconColor = MaterialTheme.colorScheme.onSecondary,
+                onClick = onRecentAppsTutorialClicked,
+                backgroundColor = MaterialTheme.colorScheme.secondary,
+                modifier =
+                    modifier.focusRequester(recentAppsFocusRequester).focusable().fillMaxSize(),
+            )
+            Spacer(modifier = Modifier.size(16.dp))
+            TutorialButton(
+                text = stringResource(R.string.touchpad_tutorial_switch_apps_gesture_button),
+                icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_apps_icon),
+                iconColor = MaterialTheme.colorScheme.primary,
+                onClick = onSwitchAppsTutorialClicked,
+                backgroundColor = MaterialTheme.colorScheme.onPrimary,
+                modifier =
+                    modifier.focusRequester(switchAppsFocusRequester).focusable().fillMaxSize(),
+            )
+        }
+    }
+}
+
+@Composable
+private fun HorizontalCompactSelectionButtons(
+    onBackTutorialClicked: () -> Unit,
+    onHomeTutorialClicked: () -> Unit,
+    onRecentAppsTutorialClicked: () -> Unit,
+    onSwitchAppsTutorialClicked: () -> Unit,
+    lastSelectedScreen: Screen,
+    modifier: Modifier = Modifier,
 ) {
     Row(
-        horizontalArrangement = Arrangement.spacedBy(20.dp),
+        horizontalArrangement = Arrangement.spacedBy(16.dp),
         verticalAlignment = Alignment.CenterVertically,
         modifier = modifier,
     ) {
@@ -133,8 +233,8 @@
             onHomeTutorialClicked,
             onRecentAppsTutorialClicked,
             onSwitchAppsTutorialClicked,
-            modifier = Modifier.weight(1f).fillMaxSize(),
             lastSelectedScreen,
+            modifier = Modifier.weight(1f).fillMaxSize(),
         )
     }
 }
@@ -145,8 +245,8 @@
     onHomeTutorialClicked: () -> Unit,
     onRecentAppsTutorialClicked: () -> Unit,
     onSwitchAppsTutorialClicked: () -> Unit,
-    modifier: Modifier = Modifier,
     lastSelectedScreen: Screen,
+    modifier: Modifier = Modifier,
 ) {
     Column(
         verticalArrangement = Arrangement.spacedBy(16.dp),
@@ -158,8 +258,8 @@
             onHomeTutorialClicked,
             onRecentAppsTutorialClicked,
             onSwitchAppsTutorialClicked,
-            modifier = Modifier.weight(1f).fillMaxSize(),
             lastSelectedScreen,
+            modifier = Modifier.weight(1f).fillMaxSize(),
         )
     }
 }
@@ -170,8 +270,8 @@
     onHomeTutorialClicked: () -> Unit,
     onRecentAppsTutorialClicked: () -> Unit,
     onSwitchAppsTutorialClicked: () -> Unit,
-    modifier: Modifier = Modifier,
     lastSelectedScreen: Screen,
+    modifier: Modifier = Modifier,
 ) {
     val homeFocusRequester = remember { FocusRequester() }
     val backFocusRequester = remember { FocusRequester() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
index 2bd104d..48b801c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
 
 val Kosmos.authenticationInteractor by
@@ -29,5 +30,6 @@
             backgroundDispatcher = testDispatcher,
             repository = authenticationRepository,
             selectedUserInteractor = selectedUserInteractor,
+            tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"),
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/BatteryRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/BatteryRepositoryKosmos.kt
new file mode 100644
index 0000000..edfe8ec
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/BatteryRepositoryKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2025 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.common.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.batteryRepository: BatteryRepository by Kosmos.Fixture { FakeBatteryRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakeBatteryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakeBatteryRepository.kt
new file mode 100644
index 0000000..ac94335
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakeBatteryRepository.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2025 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.common.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeBatteryRepository : BatteryRepository {
+    private val _isDevicePluggedIn = MutableStateFlow(false)
+
+    override val isDevicePluggedIn: Flow<Boolean> = _isDevicePluggedIn.asStateFlow()
+
+    fun setDevicePluggedIn(isPluggedIn: Boolean) {
+        _isDevicePluggedIn.value = isPluggedIn
+    }
+}
+
+val BatteryRepository.fake
+    get() = this as FakeBatteryRepository
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/BatteryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/BatteryInteractorKosmos.kt
new file mode 100644
index 0000000..2153955
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/BatteryInteractorKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2025 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.common.domain.interactor
+
+import com.android.systemui.common.data.repository.batteryRepository
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.batteryInteractor by Kosmos.Fixture { BatteryInteractor(batteryRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt
index 1636257..603160d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt
@@ -25,7 +25,8 @@
 /** Fake implementation of [CommunalPrefsRepository] */
 class FakeCommunalPrefsRepository : CommunalPrefsRepository {
     private val _isCtaDismissed = MutableStateFlow<Set<UserInfo>>(emptySet())
-    private val _isHubOnboardingismissed = MutableStateFlow<Set<UserInfo>>(emptySet())
+    private val _isHubOnboardingDismissed = MutableStateFlow<Set<UserInfo>>(emptySet())
+    private val _isDreamButtonTooltipDismissed = MutableStateFlow<Set<UserInfo>>(emptySet())
 
     override fun isCtaDismissed(user: UserInfo): Flow<Boolean> =
         _isCtaDismissed.map { it.contains(user) }
@@ -35,10 +36,18 @@
     }
 
     override fun isHubOnboardingDismissed(user: UserInfo): Flow<Boolean> =
-        _isHubOnboardingismissed.map { it.contains(user) }
+        _isHubOnboardingDismissed.map { it.contains(user) }
 
     override suspend fun setHubOnboardingDismissed(user: UserInfo) {
-        _isHubOnboardingismissed.value =
-            _isHubOnboardingismissed.value.toMutableSet().apply { add(user) }
+        _isHubOnboardingDismissed.value =
+            _isHubOnboardingDismissed.value.toMutableSet().apply { add(user) }
+    }
+
+    override fun isDreamButtonTooltipDismissed(user: UserInfo): Flow<Boolean> =
+        _isDreamButtonTooltipDismissed.map { it.contains(user) }
+
+    override suspend fun setDreamButtonTooltipDismissed(user: UserInfo) {
+        _isDreamButtonTooltipDismissed.value =
+            _isDreamButtonTooltipDismissed.value.toMutableSet().apply { add(user) }
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 89aad4b..b0a6de1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -19,10 +19,13 @@
 import android.content.testableContext
 import android.os.userManager
 import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.common.domain.interactor.batteryInteractor
 import com.android.systemui.communal.data.repository.communalMediaRepository
 import com.android.systemui.communal.data.repository.communalSmartspaceRepository
 import com.android.systemui.communal.data.repository.communalWidgetRepository
+import com.android.systemui.communal.posturing.domain.interactor.posturingInteractor
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
+import com.android.systemui.dock.dockManager
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -64,6 +67,9 @@
         logBuffer = logcatLogBuffer("CommunalInteractor"),
         tableLogBuffer = mock(),
         managedProfileController = fakeManagedProfileController,
+        batteryInteractor = batteryInteractor,
+        dockManager = dockManager,
+        posturingInteractor = posturingInteractor,
     )
 }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt
index c2d2392..43d3eb7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt
@@ -18,6 +18,7 @@
 
 import android.service.dream.dreamManager
 import com.android.internal.logging.uiEventLogger
+import com.android.systemui.communal.domain.interactor.communalPrefsInteractor
 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testDispatcher
@@ -29,6 +30,7 @@
         CommunalToDreamButtonViewModel(
             backgroundContext = testDispatcher,
             batteryController = batteryController,
+            prefsInteractor = communalPrefsInteractor,
             settingsInteractor = communalSettingsInteractor,
             activityStarter = activityStarter,
             dreamManager = dreamManager,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
index 1d3fd30..c927b55 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.keyguard.dismissCallbackRegistry
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.scene.domain.interactor.sceneBackInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 
@@ -36,5 +37,6 @@
             alternateBouncerInteractor = alternateBouncerInteractor,
             dismissCallbackRegistry = dismissCallbackRegistry,
             sceneBackInteractor = sceneBackInteractor,
+            tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"),
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
index e4c7df6..9e36428 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
 
@@ -40,6 +41,7 @@
             systemPropertiesHelper = fakeSystemPropertiesHelper,
             userAwareSecureSettingsRepository = userAwareSecureSettingsRepository,
             keyguardInteractor = keyguardInteractor,
+            tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"),
         )
         .apply { activateIn(testScope) }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index b07de16..ff7a06c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
@@ -41,5 +43,7 @@
             communalSettingsInteractor = communalSettingsInteractor,
             swipeToDismissInteractor = swipeToDismissInteractor,
             keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+            communalInteractor = communalInteractor,
+            communalSceneInteractor = communalSceneInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt
index e46ede6..e9ba425 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.scene.sceneContainerConfig
 import com.android.systemui.scene.shared.logger.sceneLogger
 
@@ -25,5 +26,6 @@
     SceneBackInteractor(
         logger = sceneLogger,
         sceneContainerConfig = sceneContainerConfig,
+        tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"),
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
index d105326..65bfafb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.model.sysUiState
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.interactor.disabledContentInteractor
@@ -89,5 +90,6 @@
         disabledContentInteractor = disabledContentInteractor,
         activityTransitionAnimator = activityTransitionAnimator,
         shadeModeInteractor = shadeModeInteractor,
+        tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"),
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt
index a4631f1..2ba9c80 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.res.R
 import com.android.systemui.shade.data.repository.fakeShadeRepository
 import com.android.systemui.shade.data.repository.shadeRepository
@@ -31,6 +32,7 @@
         applicationScope = applicationCoroutineScope,
         repository = shadeRepository,
         secureSettingsRepository = fakeSecureSettingsRepository,
+        tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"),
     )
 }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index fda57d6..e422fef 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -31,6 +31,7 @@
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.provider.Settings;
+import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -71,9 +72,9 @@
  */
 class AccessibilityInputFilter extends InputFilter implements EventStreamTransformation {
 
-    private static final String TAG = AccessibilityInputFilter.class.getSimpleName();
+    private static final String TAG = "A11yInputFilter";
 
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     /**
      * Flag for enabling the screen magnification feature.
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 8e0a778..67fdca4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -36,6 +36,7 @@
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_USER_BROADCAST_RECEIVER;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
 import static android.content.Context.DEVICE_ID_DEFAULT;
+import static android.hardware.input.InputSettings.isRepeatKeysFeatureFlagEnabled;
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
@@ -156,6 +157,7 @@
 import android.view.MagnificationSpec;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
+import android.view.ViewConfiguration;
 import android.view.WindowInfo;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
@@ -3494,6 +3496,7 @@
         somethingChanged |= readMagnificationFollowTypingLocked(userState);
         somethingChanged |= readAlwaysOnMagnificationLocked(userState);
         somethingChanged |= readMouseKeysEnabledLocked(userState);
+        somethingChanged |= readRepeatKeysSettingsLocked(userState);
         return somethingChanged;
     }
 
@@ -5771,6 +5774,12 @@
         private final Uri mUserSetupCompleteUri = Settings.Secure.getUriFor(
                 Settings.Secure.USER_SETUP_COMPLETE);
 
+        private final Uri mRepeatKeysEnabledUri = Settings.Secure.getUriFor(
+                Settings.Secure.KEY_REPEAT_ENABLED);
+
+        private final Uri mRepeatKeysTimeoutMsUri = Settings.Secure.getUriFor(
+                Settings.Secure.KEY_REPEAT_TIMEOUT_MS);
+
         public AccessibilityContentObserver(Handler handler) {
             super(handler);
         }
@@ -5827,6 +5836,12 @@
                     mNavigationModeUri, false, this, UserHandle.USER_ALL);
             contentResolver.registerContentObserver(
                     mUserSetupCompleteUri, false, this, UserHandle.USER_ALL);
+            if (isRepeatKeysFeatureFlagEnabled() && Flags.enableMagnificationKeyboardControl()) {
+                contentResolver.registerContentObserver(
+                        mRepeatKeysEnabledUri, false, this, UserHandle.USER_ALL);
+                contentResolver.registerContentObserver(
+                        mRepeatKeysTimeoutMsUri, false, this, UserHandle.USER_ALL);
+            }
         }
 
         @Override
@@ -5917,6 +5932,9 @@
                     }
                 } else if (mNavigationModeUri.equals(uri) || mUserSetupCompleteUri.equals(uri)) {
                     updateShortcutsForCurrentNavigationMode();
+                } else if (mRepeatKeysEnabledUri.equals(uri)
+                        || mRepeatKeysTimeoutMsUri.equals(uri)) {
+                    readRepeatKeysSettingsLocked(userState);
                 }
             }
         }
@@ -6055,6 +6073,24 @@
         return false;
     }
 
+    boolean readRepeatKeysSettingsLocked(AccessibilityUserState userState) {
+        if (!isRepeatKeysFeatureFlagEnabled() || !Flags.enableMagnificationKeyboardControl()) {
+            return false;
+        }
+        final boolean isRepeatKeysEnabled = Settings.Secure.getIntForUser(
+                mContext.getContentResolver(),
+                Settings.Secure.KEY_REPEAT_ENABLED,
+                1, userState.mUserId) == 1;
+        final int repeatKeysTimeoutMs = Settings.Secure.getIntForUser(
+                mContext.getContentResolver(), Settings.Secure.KEY_REPEAT_TIMEOUT_MS,
+                ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT, userState.mUserId);
+        mMagnificationController.setRepeatKeysEnabled(isRepeatKeysEnabled);
+        mMagnificationController.setRepeatKeysTimeoutMs(repeatKeysTimeoutMs);
+
+        // No need to update any other state, so always return false.
+        return false;
+    }
+
     boolean readMouseKeysEnabledLocked(AccessibilityUserState userState) {
         if (!keyboardA11yMouseKeys()) {
             return false;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index a3fe9ec..6cba363 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -554,7 +554,8 @@
                     if (motionEventInjector != null
                             && mWindowManagerService.isTouchOrFaketouchDevice()) {
                         motionEventInjector.injectEvents(
-                                gestureSteps.getList(), mClient, sequence, displayId);
+                                gestureSteps.getList(), mClient, sequence, displayId,
+                                mAccessibilityServiceInfo.isAccessibilityTool());
                     } else {
                         try {
                             if (svcClientTracingEnabled()) {
diff --git a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
index 5cbd1a2..b216953 100644
--- a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
+++ b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
@@ -105,12 +105,14 @@
      * either complete or cancelled.
      */
     public void injectEvents(List<GestureStep> gestureSteps,
-            IAccessibilityServiceClient serviceInterface, int sequence, int displayId) {
+            IAccessibilityServiceClient serviceInterface, int sequence, int displayId,
+            boolean fromAccessibilityTool) {
         SomeArgs args = SomeArgs.obtain();
         args.arg1 = gestureSteps;
         args.arg2 = serviceInterface;
         args.argi1 = sequence;
         args.argi2 = displayId;
+        args.argi3 = fromAccessibilityTool ? 1 : 0;
         mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_INJECT_EVENTS, args));
     }
 
@@ -132,9 +134,11 @@
             return;
         }
         cancelAnyPendingInjectedEvents();
-        // Indicate that the input event is injected from accessibility, to let applications
-        // distinguish it from events injected by other means.
-        policyFlags |= WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY;
+        if (!android.view.accessibility.Flags.preventA11yNontoolFromInjectingIntoSensitiveViews()) {
+            // Indicate that the input event is injected from accessibility, to let applications
+            // distinguish it from events injected by other means.
+            policyFlags |= WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY;
+        }
         sendMotionEventToNext(event, rawEvent, policyFlags);
     }
 
@@ -159,8 +163,12 @@
     public boolean handleMessage(Message message) {
         if (message.what == MESSAGE_INJECT_EVENTS) {
             SomeArgs args = (SomeArgs) message.obj;
-            injectEventsMainThread((List<GestureStep>) args.arg1,
-                    (IAccessibilityServiceClient) args.arg2, args.argi1, args.argi2);
+            injectEventsMainThread(
+                    /*gestureSteps=*/(List<GestureStep>) args.arg1,
+                    /*serviceInterface=*/(IAccessibilityServiceClient) args.arg2,
+                    /*sequence=*/args.argi1,
+                    /*displayId=*/args.argi2,
+                    /*fromAccessibilityTool=*/args.argi3 == 1);
             args.recycle();
             return true;
         }
@@ -169,9 +177,15 @@
             return false;
         }
         MotionEvent motionEvent = (MotionEvent) message.obj;
-        sendMotionEventToNext(motionEvent, motionEvent,
-                WindowManagerPolicyConstants.FLAG_PASS_TO_USER
-                | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY);
+        int policyFlags = WindowManagerPolicyConstants.FLAG_PASS_TO_USER
+                | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY;
+        if (android.view.accessibility.Flags.preventA11yNontoolFromInjectingIntoSensitiveViews()) {
+            boolean fromAccessibilityTool = message.arg2 == 1;
+            if (fromAccessibilityTool) {
+                policyFlags |= WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL;
+            }
+        }
+        sendMotionEventToNext(motionEvent, motionEvent, policyFlags);
         boolean isEndOfSequence = message.arg1 != 0;
         if (isEndOfSequence) {
             notifyService(mServiceInterfaceForCurrentGesture, mSequencesInProgress.get(0), true);
@@ -181,7 +195,8 @@
     }
 
     private void injectEventsMainThread(List<GestureStep> gestureSteps,
-            IAccessibilityServiceClient serviceInterface, int sequence, int displayId) {
+            IAccessibilityServiceClient serviceInterface, int sequence, int displayId,
+            boolean fromAccessibilityTool) {
         if (mIsDestroyed) {
             try {
                 serviceInterface.onPerformGestureResult(sequence, false);
@@ -228,7 +243,8 @@
             event.setDisplayId(displayId);
             int isEndOfSequence = (i == events.size() - 1) ? 1 : 0;
             Message message = mHandler.obtainMessage(
-                    MESSAGE_SEND_MOTION_EVENT, isEndOfSequence, 0, event);
+                    MESSAGE_SEND_MOTION_EVENT, isEndOfSequence,
+                    fromAccessibilityTool ? 1 : 0, event);
             mLastScheduledEventTime = event.getEventTime();
             mHandler.sendMessageDelayed(message, Math.max(0, event.getEventTime() - currentTime));
         }
@@ -322,9 +338,16 @@
             long now = SystemClock.uptimeMillis();
             MotionEvent cancelEvent =
                     obtainMotionEvent(now, now, MotionEvent.ACTION_CANCEL, getLastTouchPoints(), 1);
-            sendMotionEventToNext(cancelEvent, cancelEvent,
-                    WindowManagerPolicyConstants.FLAG_PASS_TO_USER
-                    | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY);
+            int policyFlags = WindowManagerPolicyConstants.FLAG_PASS_TO_USER
+                    | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY;
+            if (android.view.accessibility.Flags
+                    .preventA11yNontoolFromInjectingIntoSensitiveViews()) {
+                // ACTION_CANCEL events are internal system details for event stream state
+                // management and not used for performing new actions, so always treat them as
+                // originating from an accessibility tool.
+                policyFlags |= WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL;
+            }
+            sendMotionEventToNext(cancelEvent, cancelEvent, policyFlags);
             mOpenGesturesInProgress.put(source, false);
         }
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 486f1f4..e757dd5 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -50,6 +50,7 @@
 import android.util.SparseLongArray;
 import android.util.TypedValue;
 import android.view.Display;
+import android.view.ViewConfiguration;
 import android.view.accessibility.MagnificationAnimationCallback;
 
 import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
@@ -122,9 +123,8 @@
     private @ZoomDirection int mActiveZoomDirection = ZOOM_DIRECTION_IN;
     private int mActiveZoomDisplay = Display.INVALID_DISPLAY;
 
-    // TODO(b/355499907): Get initial repeat interval from repeat keys settings.
-    @VisibleForTesting
-    public static final int INITIAL_KEYBOARD_REPEAT_INTERVAL_MS = 500;
+    private int mInitialKeyboardRepeatIntervalMs =
+            ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
     @VisibleForTesting
     public static final int KEYBOARD_REPEAT_INTERVAL_MS = 60;
 
@@ -321,12 +321,6 @@
         mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag(context);
         mAlwaysOnMagnificationFeatureFlag.addOnChangedListener(
                 mBackgroundExecutor, mAms::updateAlwaysOnMagnification);
-
-        // TODO(b/355499907): Add an observer for repeat keys enabled changes,
-        // rather than initializing once at startup.
-        mRepeatKeysEnabled = Settings.Secure.getIntForUser(
-                mContext.getContentResolver(), Settings.Secure.KEY_REPEAT_ENABLED, 1,
-                UserHandle.USER_CURRENT) != 0;
     }
 
     @VisibleForTesting
@@ -383,7 +377,7 @@
         if (mRepeatKeysEnabled) {
             mHandler.sendMessageDelayed(
                     PooledLambda.obtainMessage(MagnificationController::maybeContinuePan, this),
-                    INITIAL_KEYBOARD_REPEAT_INTERVAL_MS);
+                    mInitialKeyboardRepeatIntervalMs);
         }
     }
 
@@ -404,7 +398,7 @@
         if (mRepeatKeysEnabled) {
             mHandler.sendMessageDelayed(
                     PooledLambda.obtainMessage(MagnificationController::maybeContinueZoom, this),
-                    INITIAL_KEYBOARD_REPEAT_INTERVAL_MS);
+                    mInitialKeyboardRepeatIntervalMs);
         }
     }
 
@@ -434,6 +428,19 @@
         }
     }
 
+    public void setRepeatKeysEnabled(boolean isRepeatKeysEnabled) {
+        mRepeatKeysEnabled = isRepeatKeysEnabled;
+    }
+
+    public void setRepeatKeysTimeoutMs(int repeatKeysTimeoutMs) {
+        mInitialKeyboardRepeatIntervalMs = repeatKeysTimeoutMs;
+    }
+
+    @VisibleForTesting
+    public int getInitialKeyboardRepeatIntervalMs() {
+        return mInitialKeyboardRepeatIntervalMs;
+    }
+
     private void handleUserInteractionChanged(int displayId, int mode) {
         if (mMagnificationCapabilities != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) {
             return;
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index aef1c08..d47aab0 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -4673,12 +4673,6 @@
                         keep.add(providerId);
                         // Use the new AppWidgetProviderInfo.
                         provider.setPartialInfoLocked(info);
-                        // Clear old previews
-                        if (remoteViewsProto()) {
-                            clearGeneratedPreviewsAsync(provider);
-                        } else {
-                            provider.clearGeneratedPreviewsLocked();
-                        }
                         // If it's enabled
                         final int M = provider.widgets.size();
                         if (M > 0) {
@@ -5104,6 +5098,10 @@
         AndroidFuture<RemoteViews> result = new AndroidFuture<>();
         mSavePreviewsHandler.post(() -> {
             SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider);
+            if (previews.size() == 0 && provider.info.generatedPreviewCategories != 0) {
+                // Failed to read previews from file, clear the file and update providers.
+                saveGeneratedPreviews(provider, previews, /* notify= */ true);
+            }
             for (int i = 0; i < previews.size(); i++) {
                 if ((widgetCategory & previews.keyAt(i)) != 0) {
                     result.complete(previews.valueAt(i));
@@ -5222,8 +5220,14 @@
                 continue;
             }
             ProtoInputStream input = new ProtoInputStream(previewsFile.readFully());
-            provider.info.generatedPreviewCategories = readGeneratedPreviewCategoriesFromProto(
-                    input);
+            try {
+                provider.info.generatedPreviewCategories = readGeneratedPreviewCategoriesFromProto(
+                        input);
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to read generated previews from file for " + provider, e);
+                previewsFile.delete();
+                provider.info.generatedPreviewCategories = 0;
+            }
             if (DEBUG) {
                 Slog.i(TAG, TextUtils.formatSimple(
                         "loadGeneratedPreviewCategoriesLocked %d %s categories %d", profileId,
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index 3de84f1..d2db8f7 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -27,7 +27,6 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Handler;
-import android.os.Message;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.os.UEventObserver;
@@ -37,6 +36,7 @@
 import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FrameworkStatsLog;
@@ -57,8 +57,6 @@
 final class DockObserver extends SystemService {
     private static final String TAG = "DockObserver";
 
-    private static final int MSG_DOCK_STATE_CHANGED = 0;
-
     private final PowerManager mPowerManager;
     private final PowerManager.WakeLock mWakeLock;
 
@@ -66,11 +64,16 @@
 
     private boolean mSystemReady;
 
+    @GuardedBy("mLock")
     private int mActualDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
 
+    @GuardedBy("mLock")
     private int mReportedDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+
+    @GuardedBy("mLock")
     private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
 
+    @GuardedBy("mLock")
     private boolean mUpdatesStopped;
 
     private final boolean mKeepDreamingWhenUnplugging;
@@ -182,18 +185,24 @@
                 ExtconInfo.EXTCON_DOCK
         });
 
-        if (!infos.isEmpty()) {
-            ExtconInfo info = infos.get(0);
-            Slog.i(TAG, "Found extcon info devPath: " + info.getDevicePath()
-                        + ", statePath: " + info.getStatePath());
+        synchronized (mLock) {
+            if (!infos.isEmpty()) {
+                ExtconInfo info = infos.get(0);
+                Slog.i(
+                        TAG,
+                        "Found extcon info devPath: "
+                                + info.getDevicePath()
+                                + ", statePath: "
+                                + info.getStatePath());
 
-            // set initial status
-            setDockStateFromProviderLocked(ExtconStateProvider.fromFile(info.getStatePath()));
-            mPreviousDockState = mActualDockState;
+                // set initial status
+                setDockStateFromProviderLocked(ExtconStateProvider.fromFile(info.getStatePath()));
+                mPreviousDockState = mActualDockState;
 
-            mExtconUEventObserver.startObserving(info);
-        } else {
-            Slog.i(TAG, "No extcon dock device found in this kernel.");
+                mExtconUEventObserver.startObserving(info);
+            } else {
+                Slog.i(TAG, "No extcon dock device found in this kernel.");
+            }
         }
 
         mDockObserverLocalService = new DockObserverLocalService();
@@ -223,13 +232,15 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void updateIfDockedLocked() {
         // don't bother broadcasting undocked here
         if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
-            updateLocked();
+            postWakefulDockStateChange();
         }
     }
 
+    @GuardedBy("mLock")
     private void setActualDockStateLocked(int newState) {
         mActualDockState = newState;
         if (!mUpdatesStopped) {
@@ -237,6 +248,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void setDockStateLocked(int newState) {
         if (newState != mReportedDockState) {
             mReportedDockState = newState;
@@ -246,10 +258,12 @@
             if (mSystemReady) {
                 // Wake up immediately when docked or undocked unless prohibited from doing so.
                 if (allowWakeFromDock()) {
-                    mPowerManager.wakeUp(SystemClock.uptimeMillis(),
+                    mPowerManager.wakeUp(
+                            SystemClock.uptimeMillis(),
+                            PowerManager.WAKE_REASON_DOCK,
                             "android.server:DOCK");
                 }
-                updateLocked();
+                postWakefulDockStateChange();
             }
         }
     }
@@ -263,9 +277,8 @@
                 Settings.Global.THEATER_MODE_ON, 0) == 0);
     }
 
-    private void updateLocked() {
-        mWakeLock.acquire();
-        mHandler.sendEmptyMessage(MSG_DOCK_STATE_CHANGED);
+    private void postWakefulDockStateChange() {
+        mHandler.post(mWakeLock.wrap(this::handleDockStateChange));
     }
 
     private void handleDockStateChange() {
@@ -348,17 +361,7 @@
         }
     }
 
-    private final Handler mHandler = new Handler(true /*async*/) {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_DOCK_STATE_CHANGED:
-                    handleDockStateChange();
-                    mWakeLock.release();
-                    break;
-            }
-        }
-    };
+    private final Handler mHandler = new Handler(true /*async*/);
 
     private int getDockedStateExtraValue(ExtconStateProvider state) {
         for (ExtconStateConfig config : mExtconStateConfigs) {
@@ -386,6 +389,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void setDockStateFromProviderLocked(ExtconStateProvider provider) {
         int state = Intent.EXTRA_DOCK_STATE_UNDOCKED;
         if ("1".equals(provider.getValue("DOCK"))) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0603c45..6ece265 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19397,13 +19397,13 @@
         if (((intent.getExtendedFlags() & Intent.EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED) == 0)
                 && intent.getExtras() != null && intent.getExtras().hasIntent()) {
             Slog.wtf(TAG,
-                    "[IntentRedirect] The intent does not have its nested keys collected as a "
+                    "[IntentRedirect Hardening] The intent does not have its nested keys collected as a "
                             + "preparation for creating intent creator tokens. Intent: "
                             + intent + "; creatorPackage: " + creatorPackage);
             if (preventIntentRedirectShowToastIfNestedKeysNotCollectedRW()) {
                 UiThread.getHandler().post(
                         () -> Toast.makeText(mContext,
-                                "Nested keys not collected. go/report-bug-intentRedir to report a"
+                                "Nested keys not collected, activity launch won't be blocked. go/report-bug-intentRedir to report a"
                                         + " bug", Toast.LENGTH_LONG).show());
             }
             if (preventIntentRedirectThrowExceptionIfNestedKeysNotCollected()) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index d335529..ce526e5 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -572,6 +572,7 @@
         public long mTotalAnonMemFreedKBs;
         public long mSumOrigAnonRss;
         public double mMaxCompactEfficiency;
+        public double mMaxSwapEfficiency;
 
         // Cpu time
         public long mTotalCpuTimeMillis;
@@ -586,6 +587,10 @@
             if (compactEfficiency > mMaxCompactEfficiency) {
                 mMaxCompactEfficiency = compactEfficiency;
             }
+            final double swapEfficiency = anonRssSaved / (double) origAnonRss;
+            if (swapEfficiency > mMaxSwapEfficiency) {
+                mMaxSwapEfficiency = swapEfficiency;
+            }
             mTotalDeltaAnonRssKBs += anonRssSaved;
             mTotalZramConsumedKBs += zramConsumed;
             mTotalAnonMemFreedKBs += memFreed;
@@ -628,7 +633,11 @@
                 pw.println("    -----Memory Stats----");
                 pw.println("    Total Delta Anon RSS (KB) : " + mTotalDeltaAnonRssKBs);
                 pw.println("    Total Physical ZRAM Consumed (KB): " + mTotalZramConsumedKBs);
+                // Anon Mem Freed = Delta Anon RSS - ZRAM Consumed
                 pw.println("    Total Anon Memory Freed (KB): " + mTotalAnonMemFreedKBs);
+                pw.println("    Avg Swap Efficiency (KB) (Delta Anon RSS/Orig Anon RSS): "
+                        + (mTotalDeltaAnonRssKBs / (double) mSumOrigAnonRss));
+                pw.println("    Max Swap Efficiency: " + mMaxSwapEfficiency);
                 // This tells us how much anon memory we were able to free thanks to running
                 // compaction
                 pw.println("    Avg Compaction Efficiency (Anon Freed/Anon RSS): "
@@ -808,8 +817,9 @@
             pw.println("  Tracking last compaction stats for " + mLastCompactionStats.size()
                     + " processes.");
             pw.println("Last Compaction per process stats:");
-            pw.println("    (ProcessName,Source,DeltaAnonRssKBs,ZramConsumedKBs,AnonMemFreedKBs,"
-                    + "CompactEfficiency,CompactCost(ms/MB),procState,oomAdj,oomAdjReason)");
+            pw.println("    (ProcessName,Source,DeltaAnonRssKBs,ZramConsumedKBs,AnonMemFreedKBs"
+                    + ",SwapEfficiency,CompactEfficiency,CompactCost(ms/MB),procState,oomAdj,"
+                    + "oomAdjReason)");
             for (Map.Entry<Integer, SingleCompactionStats> entry :
                     mLastCompactionStats.entrySet()) {
                 SingleCompactionStats stats = entry.getValue();
@@ -818,7 +828,8 @@
             pw.println();
             pw.println("Last 20 Compactions Stats:");
             pw.println("    (ProcessName,Source,DeltaAnonRssKBs,ZramConsumedKBs,AnonMemFreedKBs,"
-                    + "CompactEfficiency,CompactCost(ms/MB),procState,oomAdj,oomAdjReason)");
+                    + "SwapEfficiency,CompactEfficiency,CompactCost(ms/MB),procState,oomAdj,"
+                    + "oomAdjReason)");
             for (SingleCompactionStats stats : mCompactionStatsHistory) {
                 stats.dump(pw);
             }
@@ -1779,6 +1790,8 @@
 
         double getCompactEfficiency() { return mAnonMemFreedKBs / (double) mOrigAnonRss; }
 
+        double getSwapEfficiency() { return mDeltaAnonRssKBs / (double) mOrigAnonRss; }
+
         double getCompactCost() {
             // mCpuTimeMillis / (anonMemFreedKBs/1024) and metric is in (ms/MB)
             return mCpuTimeMillis / (double) mAnonMemFreedKBs * 1024;
@@ -1791,7 +1804,8 @@
         @NeverCompile
         void dump(PrintWriter pw) {
             pw.println("    (" + mProcessName + "," + mSourceType.name() + "," + mDeltaAnonRssKBs
-                    + "," + mZramConsumedKBs + "," + mAnonMemFreedKBs + "," + getCompactEfficiency()
+                    + "," + mZramConsumedKBs + "," + mAnonMemFreedKBs + ","
+                    + getSwapEfficiency() + "," + getCompactEfficiency()
                     + "," + getCompactCost() + "," + mProcState + "," + mOomAdj + ","
                     + OomAdjuster.oomAdjReasonToString(mOomAdjReason) + ")");
         }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5f118cb..f283009 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -837,6 +837,7 @@
 
     private final Executor mAudioServerLifecycleExecutor;
     private long mSysPropListenerNativeHandle;
+    private CacheWatcher mCacheWatcher;
     private final List<Future> mScheduledPermissionTasks = new ArrayList();
 
     private IMediaProjectionManager mProjectionService; // to validate projection token
@@ -11093,31 +11094,26 @@
                     }, getAudioPermissionsDelay(), TimeUnit.MILLISECONDS));
                 }
             };
-            mSysPropListenerNativeHandle = mAudioSystem.listenForSystemPropertyChange(
-                    PermissionManager.CACHE_KEY_PACKAGE_INFO_NOTIFY,
-                    task);
+            if (PropertyInvalidatedCache.separatePermissionNotificationsEnabled()) {
+                mCacheWatcher = new CacheWatcher(task);
+                mCacheWatcher.start();
+            } else {
+                mSysPropListenerNativeHandle = mAudioSystem.listenForSystemPropertyChange(
+                        PermissionManager.CACHE_KEY_PACKAGE_INFO_NOTIFY,
+                        task);
+            }
         } else {
             mAudioSystem.listenForSystemPropertyChange(
                     PermissionManager.CACHE_KEY_PACKAGE_INFO_NOTIFY,
                     () -> mAudioServerLifecycleExecutor.execute(
                                 mPermissionProvider::onPermissionStateChanged));
         }
-
-        if (PropertyInvalidatedCache.separatePermissionNotificationsEnabled()) {
-            new PackageInfoTransducer().start();
-        }
     }
 
     /**
-     * A transducer that converts high-speed changes in the CACHE_KEY_PACKAGE_INFO_CACHE
-     * PropertyInvalidatedCache into low-speed changes in the CACHE_KEY_PACKAGE_INFO_NOTIFY system
-     * property.  This operates on the popcorn principle: changes in the source are done when the
-     * source has been quiet for the soak interval.
-     *
-     * TODO(b/381097912) This is a temporary measure to support migration away from sysprop
-     * sniffing.  It should be cleaned up.
+     * Listens for CACHE_KEY_PACKAGE_INFO_CACHE invalidations to trigger permission syncing
      */
-    private static class PackageInfoTransducer extends Thread {
+    private static class CacheWatcher extends Thread {
 
         // The run/stop signal.
         private final AtomicBoolean mRunning = new AtomicBoolean(false);
@@ -11125,81 +11121,33 @@
         // The source of change information.
         private final PropertyInvalidatedCache.NonceWatcher mWatcher;
 
-        // The handler for scheduling delayed reactions to changes.
-        private final Handler mHandler;
+        // Task to trigger when cache changes
+        private final Runnable mTask;
 
-        // How long to soak changes: 50ms is the legacy choice.
-        private final static long SOAK_TIME_MS = 50;
-
-        // The ubiquitous lock.
-        private final Object mLock = new Object();
-
-        // If positive, this is the soak expiration time.
-        @GuardedBy("mLock")
-        private long mSoakDeadlineMs = -1;
-
-        // A source of unique long values.
-        @GuardedBy("mLock")
-        private long mToken = 0;
-
-        PackageInfoTransducer() {
-            mWatcher = PropertyInvalidatedCache
-                       .getNonceWatcher(PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE);
-            mHandler = new Handler(BackgroundThread.getHandler().getLooper()) {
-                    @Override
-                    public void handleMessage(Message msg) {
-                        PackageInfoTransducer.this.handleMessage(msg);
-                    }};
+        public CacheWatcher(Runnable r) {
+            mWatcher = PropertyInvalidatedCache.getNonceWatcher(
+                    PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE);
+            mTask = r;
         }
 
         public void run() {
             mRunning.set(true);
             while (mRunning.get()) {
+                doCheck();
                 try {
-                    final int changes = mWatcher.waitForChange();
-                    if (changes == 0 || !mRunning.get()) {
-                        continue;
-                    }
+                    mWatcher.waitForChange();
                 } catch (InterruptedException e) {
+                    Log.wtf(TAG, "Unexpected Interrupt", e);
                     // We don't know why the exception occurred but keep running until told to
                     // stop.
                     continue;
                 }
-                trigger();
             }
         }
 
-        @GuardedBy("mLock")
-        private void updateLocked() {
-            String n = Long.toString(mToken++);
-            SystemPropertySetter.setWithRetry(PermissionManager.CACHE_KEY_PACKAGE_INFO_NOTIFY, n);
-        }
-
-        private void trigger() {
-            synchronized (mLock) {
-                boolean alreadyQueued = mSoakDeadlineMs >= 0;
-                final long nowMs = SystemClock.uptimeMillis();
-                mSoakDeadlineMs = nowMs + SOAK_TIME_MS;
-                if (!alreadyQueued) {
-                    mHandler.sendEmptyMessageAtTime(0, mSoakDeadlineMs);
-                    updateLocked();
-                }
-            }
-        }
-
-        private void handleMessage(Message msg) {
-            synchronized (mLock) {
-                if (mSoakDeadlineMs < 0) {
-                    return;  // ???
-                }
-                final long nowMs = SystemClock.uptimeMillis();
-                if (mSoakDeadlineMs > nowMs) {
-                    mSoakDeadlineMs = nowMs + SOAK_TIME_MS;
-                    mHandler.sendEmptyMessageAtTime(0, mSoakDeadlineMs);
-                    return;
-                }
-                mSoakDeadlineMs = -1;
-                updateLocked();
+        public synchronized void doCheck() {
+            if (mWatcher.isChanged()) {
+                mTask.run();
             }
         }
 
@@ -15376,7 +15324,11 @@
     /** @see AudioManager#permissionUpdateBarrier() */
     public void permissionUpdateBarrier() {
         if (!audioserverPermissions()) return;
-        mAudioSystem.triggerSystemPropertyUpdate(mSysPropListenerNativeHandle);
+        if (PropertyInvalidatedCache.separatePermissionNotificationsEnabled()) {
+            mCacheWatcher.doCheck();
+        } else {
+            mAudioSystem.triggerSystemPropertyUpdate(mSysPropListenerNativeHandle);
+        }
         List<Future> snapshot;
         synchronized (mScheduledPermissionTasks) {
             snapshot = List.copyOf(mScheduledPermissionTasks);
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index bbd0e41..7890db1 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -509,3 +509,11 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "display_category_built_in"
+    namespace: "display_manager"
+    description: "Add a new category to get the built in displays."
+    bug: "293651324"
+    is_fixed_read_only: false
+}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index b8ce86b..2f22853 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -22,8 +22,6 @@
 import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
 import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT;
 
-import static com.android.hardware.input.Flags.keyboardLayoutManagerMultiUserImeSetup;
-
 import android.annotation.AnyThread;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
@@ -68,7 +66,6 @@
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.R;
@@ -1081,8 +1078,6 @@
         List<ImeInfo> imeInfoList = new ArrayList<>();
         UserManager userManager = Objects.requireNonNull(
                 mContext.getSystemService(UserManager.class));
-        InputMethodManager inputMethodManager = Objects.requireNonNull(
-                mContext.getSystemService(InputMethodManager.class));
         // Need to use InputMethodManagerInternal to call getEnabledInputMethodListAsUser()
         // instead of using InputMethodManager which uses enforceCallingPermissions() that
         // breaks when we are calling the method for work profile user ID since it doesn't check
@@ -1093,14 +1088,10 @@
             for (InputMethodInfo imeInfo :
                     inputMethodManagerInternal.getEnabledInputMethodListAsUser(
                             userId)) {
-                final List<InputMethodSubtype> imeSubtypes;
-                if (keyboardLayoutManagerMultiUserImeSetup()) {
-                    imeSubtypes = inputMethodManagerInternal.getEnabledInputMethodSubtypeListAsUser(
-                            imeInfo.getId(), true /* allowsImplicitlyEnabledSubtypes */, userId);
-                } else {
-                    imeSubtypes = inputMethodManager.getEnabledInputMethodSubtypeList(imeInfo,
-                            true /* allowsImplicitlyEnabledSubtypes */);
-                }
+                final List<InputMethodSubtype> imeSubtypes =
+                        inputMethodManagerInternal.getEnabledInputMethodSubtypeListAsUser(
+                                imeInfo.getId(), true /* allowsImplicitlyEnabledSubtypes */,
+                                userId);
                 for (InputMethodSubtype imeSubtype : imeSubtypes) {
                     if (!imeSubtype.isSuitableForPhysicalKeyboardLayoutMapping()) {
                         continue;
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index d23a863..d00ac4d 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -29,14 +29,13 @@
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.hardware.tv.mediaquality.AmbientBacklightColorFormat;
-import android.hardware.tv.mediaquality.DolbyAudioProcessing;
-import android.hardware.tv.mediaquality.DtsVirtualX;
 import android.hardware.tv.mediaquality.IMediaQuality;
 import android.hardware.tv.mediaquality.IPictureProfileAdjustmentListener;
 import android.hardware.tv.mediaquality.IPictureProfileChangedListener;
 import android.hardware.tv.mediaquality.ISoundProfileAdjustmentListener;
 import android.hardware.tv.mediaquality.ISoundProfileChangedListener;
 import android.hardware.tv.mediaquality.ParamCapability;
+import android.hardware.tv.mediaquality.ParameterRange;
 import android.hardware.tv.mediaquality.PictureParameter;
 import android.hardware.tv.mediaquality.PictureParameters;
 import android.hardware.tv.mediaquality.SoundParameter;
@@ -50,8 +49,6 @@
 import android.media.quality.IPictureProfileCallback;
 import android.media.quality.ISoundProfileCallback;
 import android.media.quality.MediaQualityContract.BaseParameters;
-import android.media.quality.MediaQualityContract.PictureQuality;
-import android.media.quality.MediaQualityContract.SoundQuality;
 import android.media.quality.MediaQualityManager;
 import android.media.quality.ParameterCapability;
 import android.media.quality.PictureProfile;
@@ -77,21 +74,16 @@
 import com.android.server.SystemService;
 import com.android.server.utils.Slogf;
 
-import org.json.JSONException;
-import org.json.JSONObject;
-
 import java.io.File;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.NoSuchElementException;
-import java.util.UUID;
 import java.util.stream.Collectors;
 
 /**
@@ -106,7 +98,6 @@
     private static final String PICTURE_PROFILE_PREFERENCE = "picture_profile_preference";
     private static final String SOUND_PROFILE_PREFERENCE = "sound_profile_preference";
     private static final String COMMA_DELIMITER = ",";
-    private static final int MAX_UUID_GENERATION_ATTEMPTS = 10;
     private final Context mContext;
     private final MediaQualityDbHelper mMediaQualityDbHelper;
     private final BiMap<Long, String> mPictureProfileTempIdMap;
@@ -275,7 +266,7 @@
             synchronized (mPictureProfileLock) {
                 SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
 
-                ContentValues values = getContentValues(null,
+                ContentValues values = MediaQualityUtils.getContentValues(null,
                         pp.getProfileType(),
                         pp.getName(),
                         pp.getPackageName() == null || pp.getPackageName().isEmpty()
@@ -286,7 +277,7 @@
                 // id is auto-generated by SQLite upon successful insertion of row
                 Long id = db.insert(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
                         null, values);
-                populateTempIdMap(mPictureProfileTempIdMap, id);
+                MediaQualityUtils.populateTempIdMap(mPictureProfileTempIdMap, id);
                 String value = mPictureProfileTempIdMap.getValue(id);
                 pp.setProfileId(value);
                 notifyOnPictureProfileAdded(value, pp, Binder.getCallingUid(),
@@ -308,8 +299,9 @@
         private android.hardware.tv.mediaquality.PictureProfile convertToHalPictureProfile(Long id,
                 PersistableBundle params) {
             PictureParameters pictureParameters = new PictureParameters();
-            pictureParameters.pictureParameters = convertPersistableBundleToPictureParameterList(
-                    params);
+            pictureParameters.pictureParameters =
+                    MediaQualityUtils.convertPersistableBundleToPictureParameterList(
+                            params);
 
             android.hardware.tv.mediaquality.PictureProfile toReturn =
                     new android.hardware.tv.mediaquality.PictureProfile();
@@ -329,7 +321,7 @@
             }
 
             synchronized (mPictureProfileLock) {
-                ContentValues values = getContentValues(dbId,
+                ContentValues values = MediaQualityUtils.getContentValues(dbId,
                         pp.getProfileType(),
                         pp.getName(),
                         pp.getPackageName(),
@@ -405,7 +397,7 @@
                 try (
                         Cursor cursor = getCursorAfterQuerying(
                                 mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
-                                getMediaProfileColumns(includeParams), selection,
+                                MediaQualityUtils.getMediaProfileColumns(includeParams), selection,
                                 selectionArguments)
                 ) {
                     int count = cursor.getCount();
@@ -420,7 +412,8 @@
                         return null;
                     }
                     cursor.moveToFirst();
-                    return convertCursorToPictureProfileWithTempId(cursor);
+                    return MediaQualityUtils.convertCursorToPictureProfileWithTempId(cursor,
+                            mPictureProfileTempIdMap);
                 }
             }
         }
@@ -432,7 +425,8 @@
             try (
                     Cursor cursor = getCursorAfterQuerying(
                             mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
-                            getMediaProfileColumns(false), selection, selectionArguments)
+                            MediaQualityUtils.getMediaProfileColumns(false), selection,
+                            selectionArguments)
             ) {
                 int count = cursor.getCount();
                 if (count == 0) {
@@ -445,7 +439,8 @@
                     return null;
                 }
                 cursor.moveToFirst();
-                return convertCursorToPictureProfileWithTempId(cursor);
+                return MediaQualityUtils.convertCursorToPictureProfileWithTempId(cursor,
+                        mPictureProfileTempIdMap);
             }
         }
 
@@ -463,7 +458,8 @@
                         options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false);
                 String selection = BaseParameters.PARAMETER_PACKAGE + " = ?";
                 String[] selectionArguments = {packageName};
-                return getPictureProfilesBasedOnConditions(getMediaProfileColumns(includeParams),
+                return getPictureProfilesBasedOnConditions(MediaQualityUtils
+                                .getMediaProfileColumns(includeParams),
                         selection, selectionArguments);
             }
         }
@@ -492,8 +488,8 @@
 
             try {
                 if (mMediaQuality != null) {
-                    PictureParameter[] pictureParameters =
-                            convertPersistableBundleToPictureParameterList(params);
+                    PictureParameter[] pictureParameters = MediaQualityUtils
+                            .convertPersistableBundleToPictureParameterList(params);
 
                     PictureParameters pp = new PictureParameters();
                     pp.pictureParameters = pictureParameters;
@@ -507,337 +503,6 @@
             return false;
         }
 
-        private PictureParameter[] convertPersistableBundleToPictureParameterList(
-                PersistableBundle params) {
-            if (params == null) {
-                return null;
-            }
-
-            List<PictureParameter> pictureParams = new ArrayList<>();
-            if (params.containsKey(PictureQuality.PARAMETER_BRIGHTNESS)) {
-                pictureParams.add(PictureParameter.brightness(params.getLong(
-                        PictureQuality.PARAMETER_BRIGHTNESS)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_CONTRAST)) {
-                pictureParams.add(PictureParameter.contrast(params.getInt(
-                        PictureQuality.PARAMETER_CONTRAST)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_SHARPNESS)) {
-                pictureParams.add(PictureParameter.sharpness(params.getInt(
-                        PictureQuality.PARAMETER_SHARPNESS)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_SATURATION)) {
-                pictureParams.add(PictureParameter.saturation(params.getInt(
-                        PictureQuality.PARAMETER_SATURATION)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_HUE)) {
-                pictureParams.add(PictureParameter.hue(params.getInt(
-                        PictureQuality.PARAMETER_HUE)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS)) {
-                pictureParams.add(PictureParameter.colorTunerBrightness(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION)) {
-                pictureParams.add(PictureParameter.colorTunerSaturation(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_SATURATION)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE)) {
-                pictureParams.add(PictureParameter.colorTunerHue(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_HUE)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET)) {
-                pictureParams.add(PictureParameter.colorTunerRedOffset(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET)) {
-                pictureParams.add(PictureParameter.colorTunerGreenOffset(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET)) {
-                pictureParams.add(PictureParameter.colorTunerBlueOffset(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) {
-                pictureParams.add(PictureParameter.colorTunerRedGain(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)) {
-                pictureParams.add(PictureParameter.colorTunerGreenGain(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)) {
-                pictureParams.add(PictureParameter.colorTunerBlueGain(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_NOISE_REDUCTION)) {
-                pictureParams.add(PictureParameter.noiseReduction(
-                        (byte) params.getInt(PictureQuality.PARAMETER_NOISE_REDUCTION)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)) {
-                pictureParams.add(PictureParameter.mpegNoiseReduction(
-                        (byte) params.getInt(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_FLESH_TONE)) {
-                pictureParams.add(PictureParameter.fleshTone(
-                        (byte) params.getInt(PictureQuality.PARAMETER_FLESH_TONE)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_DECONTOUR)) {
-                pictureParams.add(PictureParameter.deContour(
-                        (byte) params.getInt(PictureQuality.PARAMETER_DECONTOUR)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)) {
-                pictureParams.add(PictureParameter.dynamicLumaControl(
-                        (byte) params.getInt(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_FILM_MODE)) {
-                pictureParams.add(PictureParameter.filmMode(params.getBoolean(
-                        PictureQuality.PARAMETER_FILM_MODE)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_BLUE_STRETCH)) {
-                pictureParams.add(PictureParameter.blueStretch(params.getBoolean(
-                        PictureQuality.PARAMETER_BLUE_STRETCH)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNE)) {
-                pictureParams.add(PictureParameter.colorTune(params.getBoolean(
-                        PictureQuality.PARAMETER_COLOR_TUNE)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE)) {
-                pictureParams.add(PictureParameter.colorTemperature(
-                        (byte) params.getInt(
-                                PictureQuality.PARAMETER_COLOR_TEMPERATURE)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_GLOBAL_DIMMING)) {
-                pictureParams.add(PictureParameter.globeDimming(params.getBoolean(
-                        PictureQuality.PARAMETER_GLOBAL_DIMMING)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED)) {
-                pictureParams.add(PictureParameter.autoPictureQualityEnabled(params.getBoolean(
-                        PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)) {
-                pictureParams.add(PictureParameter.autoSuperResolutionEnabled(params.getBoolean(
-                                PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) {
-                pictureParams.add(PictureParameter.colorTemperatureRedGain(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)) {
-                pictureParams.add(PictureParameter.colorTemperatureGreenGain(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)) {
-                pictureParams.add(PictureParameter.colorTemperatureBlueGain(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_LEVEL_RANGE)) {
-                pictureParams.add(PictureParameter.levelRange(
-                        (byte) params.getInt(PictureQuality.PARAMETER_LEVEL_RANGE)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_GAMUT_MAPPING)) {
-                pictureParams.add(PictureParameter.gamutMapping(params.getBoolean(
-                        PictureQuality.PARAMETER_GAMUT_MAPPING)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_PC_MODE)) {
-                pictureParams.add(PictureParameter.pcMode(params.getBoolean(
-                        PictureQuality.PARAMETER_PC_MODE)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_LOW_LATENCY)) {
-                pictureParams.add(PictureParameter.lowLatency(params.getBoolean(
-                        PictureQuality.PARAMETER_LOW_LATENCY)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_VRR)) {
-                pictureParams.add(PictureParameter.vrr(params.getBoolean(
-                        PictureQuality.PARAMETER_VRR)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_CVRR)) {
-                pictureParams.add(PictureParameter.cvrr(params.getBoolean(
-                        PictureQuality.PARAMETER_CVRR)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_HDMI_RGB_RANGE)) {
-                pictureParams.add(PictureParameter.hdmiRgbRange(
-                        (byte) params.getInt(PictureQuality.PARAMETER_HDMI_RGB_RANGE)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_SPACE)) {
-                pictureParams.add(PictureParameter.colorSpace(
-                        (byte) params.getInt(PictureQuality.PARAMETER_COLOR_SPACE)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS)) {
-                pictureParams.add(PictureParameter.panelInitMaxLuminceNits(
-                        params.getInt(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID)) {
-                pictureParams.add(PictureParameter.panelInitMaxLuminceValid(
-                        params.getBoolean(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_GAMMA)) {
-                pictureParams.add(PictureParameter.gamma(
-                        (byte) params.getInt(PictureQuality.PARAMETER_GAMMA)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET)) {
-                pictureParams.add(PictureParameter.colorTemperatureRedOffset(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET)) {
-                pictureParams.add(PictureParameter.colorTemperatureGreenOffset(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET)) {
-                pictureParams.add(PictureParameter.colorTemperatureBlueOffset(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_RED)) {
-                pictureParams.add(PictureParameter.elevenPointRed(params.getIntArray(
-                        PictureQuality.PARAMETER_ELEVEN_POINT_RED)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_GREEN)) {
-                pictureParams.add(PictureParameter.elevenPointGreen(params.getIntArray(
-                        PictureQuality.PARAMETER_ELEVEN_POINT_GREEN)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_BLUE)) {
-                pictureParams.add(PictureParameter.elevenPointBlue(params.getIntArray(
-                        PictureQuality.PARAMETER_ELEVEN_POINT_BLUE)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)) {
-                pictureParams.add(PictureParameter.lowBlueLight(
-                        (byte) params.getInt(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_LD_MODE)) {
-                pictureParams.add(PictureParameter.LdMode(
-                        (byte) params.getInt(PictureQuality.PARAMETER_LD_MODE)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_OSD_RED_GAIN)) {
-                pictureParams.add(PictureParameter.osdRedGain(params.getInt(
-                        PictureQuality.PARAMETER_OSD_RED_GAIN)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_OSD_GREEN_GAIN)) {
-                pictureParams.add(PictureParameter.osdGreenGain(params.getInt(
-                        PictureQuality.PARAMETER_OSD_GREEN_GAIN)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_OSD_BLUE_GAIN)) {
-                pictureParams.add(PictureParameter.osdBlueGain(params.getInt(
-                        PictureQuality.PARAMETER_OSD_BLUE_GAIN)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_OSD_RED_OFFSET)) {
-                pictureParams.add(PictureParameter.osdRedOffset(params.getInt(
-                        PictureQuality.PARAMETER_OSD_RED_OFFSET)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_OSD_GREEN_OFFSET)) {
-                pictureParams.add(PictureParameter.osdGreenOffset(params.getInt(
-                        PictureQuality.PARAMETER_OSD_GREEN_OFFSET)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_OSD_BLUE_OFFSET)) {
-                pictureParams.add(PictureParameter.osdBlueOffset(params.getInt(
-                        PictureQuality.PARAMETER_OSD_BLUE_OFFSET)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_OSD_HUE)) {
-                pictureParams.add(PictureParameter.osdHue(params.getInt(
-                        PictureQuality.PARAMETER_OSD_HUE)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_OSD_SATURATION)) {
-                pictureParams.add(PictureParameter.osdSaturation(params.getInt(
-                        PictureQuality.PARAMETER_OSD_SATURATION)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_OSD_CONTRAST)) {
-                pictureParams.add(PictureParameter.osdContrast(params.getInt(
-                        PictureQuality.PARAMETER_OSD_CONTRAST)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SWITCH)) {
-                pictureParams.add(PictureParameter.colorTunerSwitch(params.getBoolean(
-                        PictureQuality.PARAMETER_COLOR_TUNER_SWITCH)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED)) {
-                pictureParams.add(PictureParameter.colorTunerHueRed(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN)) {
-                pictureParams.add(PictureParameter.colorTunerHueGreen(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE)) {
-                pictureParams.add(PictureParameter.colorTunerHueBlue(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN)) {
-                pictureParams.add(PictureParameter.colorTunerHueCyan(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA)) {
-                pictureParams.add(PictureParameter.colorTunerHueMagenta(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW)) {
-                pictureParams.add(PictureParameter.colorTunerHueYellow(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH)) {
-                pictureParams.add(PictureParameter.colorTunerHueFlesh(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED)) {
-                pictureParams.add(PictureParameter.colorTunerSaturationRed(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN)) {
-                pictureParams.add(PictureParameter.colorTunerSaturationGreen(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE)) {
-                pictureParams.add(PictureParameter.colorTunerSaturationBlue(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN)) {
-                pictureParams.add(PictureParameter.colorTunerSaturationCyan(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA)) {
-                pictureParams.add(PictureParameter.colorTunerSaturationMagenta(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW)) {
-                pictureParams.add(PictureParameter.colorTunerSaturationYellow(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH)) {
-                pictureParams.add(PictureParameter.colorTunerSaturationFlesh(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED)) {
-                pictureParams.add(PictureParameter.colorTunerLuminanceRed(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN)) {
-                pictureParams.add(PictureParameter.colorTunerLuminanceGreen(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE)) {
-                pictureParams.add(PictureParameter.colorTunerLuminanceBlue(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN)) {
-                pictureParams.add(PictureParameter.colorTunerLuminanceCyan(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA)) {
-                pictureParams.add(PictureParameter.colorTunerLuminanceMagenta(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW)) {
-                pictureParams.add(PictureParameter.colorTunerLuminanceYellow(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH)) {
-                pictureParams.add(PictureParameter.colorTunerLuminanceFlesh(params.getInt(
-                        PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH)));
-            }
-            if (params.containsKey(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE)) {
-                pictureParams.add(PictureParameter.pictureQualityEventType(
-                        (byte) params.getInt(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE)));
-            }
-            return  (PictureParameter[]) pictureParams.toArray();
-        }
-
         @GuardedBy("mPictureProfileLock")
         @Override
         public List<String> getPictureProfilePackageNames(UserHandle user) {
@@ -903,7 +568,7 @@
             synchronized (mSoundProfileLock) {
                 SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
 
-                ContentValues values = getContentValues(null,
+                ContentValues values = MediaQualityUtils.getContentValues(null,
                         sp.getProfileType(),
                         sp.getName(),
                         sp.getPackageName() == null || sp.getPackageName().isEmpty()
@@ -914,7 +579,7 @@
                 // id is auto-generated by SQLite upon successful insertion of row
                 Long id = db.insert(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
                         null, values);
-                populateTempIdMap(mSoundProfileTempIdMap, id);
+                MediaQualityUtils.populateTempIdMap(mSoundProfileTempIdMap, id);
                 String value = mSoundProfileTempIdMap.getValue(id);
                 sp.setProfileId(value);
                 notifyOnSoundProfileAdded(value, sp, Binder.getCallingUid(),
@@ -935,7 +600,8 @@
         private android.hardware.tv.mediaquality.SoundProfile convertToHalSoundProfile(Long id,
                 PersistableBundle params) {
             SoundParameters soundParameters = new SoundParameters();
-            soundParameters.soundParameters = convertPersistableBundleToSoundParameterList(params);
+            soundParameters.soundParameters =
+                    MediaQualityUtils.convertPersistableBundleToSoundParameterList(params);
 
             android.hardware.tv.mediaquality.SoundProfile toReturn =
                     new android.hardware.tv.mediaquality.SoundProfile();
@@ -955,18 +621,19 @@
             }
 
             synchronized (mSoundProfileLock) {
-                ContentValues values = getContentValues(dbId,
+                ContentValues values = MediaQualityUtils.getContentValues(dbId,
                         sp.getProfileType(),
                         sp.getName(),
                         sp.getPackageName(),
                         sp.getInputId(),
                         sp.getParameters());
 
-            SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
-            db.replace(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, null, values);
-            notifyOnSoundProfileUpdated(mSoundProfileTempIdMap.getValue(dbId),
-                    getSoundProfile(dbId), Binder.getCallingUid(), Binder.getCallingPid());
-            notifyHalOnSoundProfileChange(dbId, sp.getParameters());
+                SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
+                db.replace(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
+                        null, values);
+                notifyOnSoundProfileUpdated(mSoundProfileTempIdMap.getValue(dbId),
+                        getSoundProfile(dbId), Binder.getCallingUid(), Binder.getCallingPid());
+                notifyHalOnSoundProfileChange(dbId, sp.getParameters());
             }
         }
 
@@ -1029,7 +696,7 @@
                 try (
                         Cursor cursor = getCursorAfterQuerying(
                                 mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
-                                getMediaProfileColumns(includeParams), selection,
+                                MediaQualityUtils.getMediaProfileColumns(includeParams), selection,
                                 selectionArguments)
                 ) {
                     int count = cursor.getCount();
@@ -1044,7 +711,8 @@
                         return null;
                     }
                     cursor.moveToFirst();
-                    return convertCursorToSoundProfileWithTempId(cursor);
+                    return MediaQualityUtils.convertCursorToSoundProfileWithTempId(cursor,
+                            mSoundProfileTempIdMap);
                 }
             }
         }
@@ -1056,7 +724,8 @@
             try (
                     Cursor cursor = getCursorAfterQuerying(
                             mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
-                            getMediaProfileColumns(false), selection, selectionArguments)
+                            MediaQualityUtils.getMediaProfileColumns(false), selection,
+                            selectionArguments)
             ) {
                 int count = cursor.getCount();
                 if (count == 0) {
@@ -1069,7 +738,8 @@
                     return null;
                 }
                 cursor.moveToFirst();
-                return convertCursorToSoundProfileWithTempId(cursor);
+                return MediaQualityUtils.convertCursorToSoundProfileWithTempId(
+                        cursor, mSoundProfileTempIdMap);
             }
         }
 
@@ -1087,7 +757,8 @@
                         options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false);
                 String selection = BaseParameters.PARAMETER_PACKAGE + " = ?";
                 String[] selectionArguments = {packageName};
-                return getSoundProfilesBasedOnConditions(getMediaProfileColumns(includeParams),
+                return getSoundProfilesBasedOnConditions(MediaQualityUtils
+                                .getMediaProfileColumns(includeParams),
                         selection, selectionArguments);
             }
         }
@@ -1116,7 +787,7 @@
             try {
                 if (mMediaQuality != null) {
                     SoundParameter[] soundParameters =
-                            convertPersistableBundleToSoundParameterList(params);
+                            MediaQualityUtils.convertPersistableBundleToSoundParameterList(params);
 
                     SoundParameters sp = new SoundParameters();
                     sp.soundParameters = soundParameters;
@@ -1130,95 +801,6 @@
             return false;
         }
 
-        private SoundParameter[] convertPersistableBundleToSoundParameterList(
-                PersistableBundle params) {
-            //TODO: set EqualizerDetail
-            if (params == null) {
-                return null;
-            }
-            List<SoundParameter> soundParams = new ArrayList<>();
-            if (params.containsKey(SoundQuality.PARAMETER_BALANCE)) {
-                soundParams.add(SoundParameter.balance(params.getInt(
-                        SoundQuality.PARAMETER_BALANCE)));
-            }
-            if (params.containsKey(SoundQuality.PARAMETER_BASS)) {
-                soundParams.add(SoundParameter.bass(params.getInt(SoundQuality.PARAMETER_BASS)));
-            }
-            if (params.containsKey(SoundQuality.PARAMETER_TREBLE)) {
-                soundParams.add(SoundParameter.treble(params.getInt(
-                        SoundQuality.PARAMETER_TREBLE)));
-            }
-            if (params.containsKey(SoundQuality.PARAMETER_SURROUND_SOUND)) {
-                soundParams.add(SoundParameter.surroundSoundEnabled(params.getBoolean(
-                        SoundQuality.PARAMETER_SURROUND_SOUND)));
-            }
-            if (params.containsKey(SoundQuality.PARAMETER_SPEAKERS)) {
-                soundParams.add(SoundParameter.speakersEnabled(params.getBoolean(
-                        SoundQuality.PARAMETER_SPEAKERS)));
-            }
-            if (params.containsKey(SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS)) {
-                soundParams.add(SoundParameter.speakersDelayMs(params.getInt(
-                        SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS)));
-            }
-            if (params.containsKey(SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL)) {
-                soundParams.add(SoundParameter.autoVolumeControl(params.getBoolean(
-                        SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL)));
-            }
-            if (params.containsKey(SoundQuality.PARAMETER_DTS_DRC)) {
-                soundParams.add(SoundParameter.dtsDrc(params.getBoolean(
-                        SoundQuality.PARAMETER_DTS_DRC)));
-            }
-            if (params.containsKey(SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS)) {
-                soundParams.add(SoundParameter.surroundSoundEnabled(params.getBoolean(
-                        SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS)));
-            }
-            if (params.containsKey(SoundQuality.PARAMETER_EARC)) {
-                soundParams.add(SoundParameter.enhancedAudioReturnChannelEnabled(params.getBoolean(
-                        SoundQuality.PARAMETER_EARC)));
-            }
-            if (params.containsKey(SoundQuality.PARAMETER_DOWN_MIX_MODE)) {
-                soundParams.add(SoundParameter.downmixMode((byte) params.getInt(
-                        SoundQuality.PARAMETER_DOWN_MIX_MODE)));
-            }
-            if (params.containsKey(SoundQuality.PARAMETER_SOUND_STYLE)) {
-                soundParams.add(SoundParameter.soundStyle((byte) params.getInt(
-                        SoundQuality.PARAMETER_SOUND_STYLE)));
-            }
-            if (params.containsKey(SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE)) {
-                soundParams.add(SoundParameter.digitalOutput((byte) params.getInt(
-                        SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE)));
-            }
-            if (params.containsKey(SoundQuality.PARAMETER_DIALOGUE_ENHANCER)) {
-                soundParams.add(SoundParameter.dolbyDialogueEnhancer((byte) params.getInt(
-                        SoundQuality.PARAMETER_DIALOGUE_ENHANCER)));
-            }
-
-            DolbyAudioProcessing dab = new DolbyAudioProcessing();
-            dab.soundMode =
-                    (byte) params.getInt(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_SOUND_MODE);
-            dab.volumeLeveler =
-                    params.getBoolean(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_VOLUME_LEVELER);
-            dab.surroundVirtualizer = params.getBoolean(
-                    SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_SURROUND_VIRTUALIZER);
-            dab.dolbyAtmos =
-                    params.getBoolean(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_DOLBY_ATMOS);
-            soundParams.add(SoundParameter.dolbyAudioProcessing(dab));
-
-            DtsVirtualX dts = new DtsVirtualX();
-            dts.tbHdx = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TBHDX);
-            dts.limiter = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_LIMITER);
-            dts.truSurroundX = params.getBoolean(
-                    SoundQuality.PARAMETER_DTS_VIRTUAL_X_TRU_SURROUND_X);
-            dts.truVolumeHd = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TRU_VOLUME_HD);
-            dts.dialogClarity = params.getBoolean(
-                    SoundQuality.PARAMETER_DTS_VIRTUAL_X_DIALOG_CLARITY);
-            dts.definition = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_DEFINITION);
-            dts.height = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_HEIGHT);
-            soundParams.add(SoundParameter.dtsVirtualX(dts));
-
-            return  (SoundParameter[]) soundParams.toArray();
-        }
-
         @GuardedBy("mSoundProfileLock")
         @Override
         public List<String> getSoundProfilePackageNames(UserHandle user) {
@@ -1269,169 +851,6 @@
                     mContext.getPackageName()) == mPackageManager.PERMISSION_GRANTED;
         }
 
-        private void populateTempIdMap(BiMap<Long, String> map, Long id) {
-            if (id != null && map.getValue(id) == null) {
-                String uuid;
-                int attempts = 0;
-                while (attempts < MAX_UUID_GENERATION_ATTEMPTS) {
-                    uuid = UUID.randomUUID().toString();
-                    if (map.getKey(uuid) == null) {
-                        map.put(id, uuid);
-                        return;
-                    }
-                    attempts++;
-                }
-            }
-        }
-
-        private String persistableBundleToJson(PersistableBundle bundle) {
-            JSONObject json = new JSONObject();
-            for (String key : bundle.keySet()) {
-                Object value = bundle.get(key);
-                try {
-                    if (value instanceof String) {
-                        json.put(key, bundle.getString(key));
-                    } else if (value instanceof Integer) {
-                        json.put(key, bundle.getInt(key));
-                    } else if (value instanceof Long) {
-                        json.put(key, bundle.getLong(key));
-                    } else if (value instanceof Boolean) {
-                        json.put(key, bundle.getBoolean(key));
-                    } else if (value instanceof Double) {
-                        json.put(key, bundle.getDouble(key));
-                    }
-                } catch (JSONException e) {
-                    Log.e(TAG, "Unable to serialize ", e);
-                }
-            }
-            return json.toString();
-        }
-
-        private PersistableBundle jsonToPersistableBundle(String jsonString) {
-            PersistableBundle bundle = new PersistableBundle();
-            if (jsonString != null) {
-                JSONObject jsonObject = null;
-                try {
-                    jsonObject = new JSONObject(jsonString);
-
-                    Iterator<String> keys = jsonObject.keys();
-                    while (keys.hasNext()) {
-                        String key = keys.next();
-                        Object value = jsonObject.get(key);
-
-                        if (value instanceof String) {
-                            bundle.putString(key, (String) value);
-                        } else if (value instanceof Integer) {
-                            bundle.putInt(key, (Integer) value);
-                        } else if (value instanceof Boolean) {
-                            bundle.putBoolean(key, (Boolean) value);
-                        } else if (value instanceof Double) {
-                            bundle.putDouble(key, (Double) value);
-                        } else if (value instanceof Long) {
-                            bundle.putLong(key, (Long) value);
-                        }
-                    }
-                } catch (JSONException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-            return bundle;
-        }
-
-        private ContentValues getContentValues(Long dbId, Integer profileType, String name,
-                String packageName, String inputId, PersistableBundle params) {
-            ContentValues values = new ContentValues();
-            if (dbId != null) {
-                values.put(BaseParameters.PARAMETER_ID, dbId);
-            }
-            if (profileType != null) {
-                values.put(BaseParameters.PARAMETER_TYPE, profileType);
-            }
-            if (name != null) {
-                values.put(BaseParameters.PARAMETER_NAME, name);
-            }
-            if (packageName != null) {
-                values.put(BaseParameters.PARAMETER_PACKAGE, packageName);
-            }
-            if (inputId != null) {
-                values.put(BaseParameters.PARAMETER_INPUT_ID, inputId);
-            }
-            if (params != null) {
-                values.put(mMediaQualityDbHelper.SETTINGS, persistableBundleToJson(params));
-            }
-            return values;
-        }
-
-        private String[] getMediaProfileColumns(boolean includeParams) {
-            ArrayList<String> columns = new ArrayList<>(Arrays.asList(
-                    BaseParameters.PARAMETER_ID,
-                    BaseParameters.PARAMETER_TYPE,
-                    BaseParameters.PARAMETER_NAME,
-                    BaseParameters.PARAMETER_INPUT_ID,
-                    BaseParameters.PARAMETER_PACKAGE)
-            );
-            if (includeParams) {
-                columns.add(mMediaQualityDbHelper.SETTINGS);
-            }
-            return columns.toArray(new String[0]);
-        }
-
-        private PictureProfile convertCursorToPictureProfileWithTempId(Cursor cursor) {
-            return new PictureProfile(
-                    getTempId(mPictureProfileTempIdMap, cursor),
-                    getType(cursor),
-                    getName(cursor),
-                    getInputId(cursor),
-                    getPackageName(cursor),
-                    jsonToPersistableBundle(getSettingsString(cursor)),
-                    PictureProfileHandle.NONE
-            );
-        }
-
-        private SoundProfile convertCursorToSoundProfileWithTempId(Cursor cursor) {
-            return new SoundProfile(
-                    getTempId(mSoundProfileTempIdMap, cursor),
-                    getType(cursor),
-                    getName(cursor),
-                    getInputId(cursor),
-                    getPackageName(cursor),
-                    jsonToPersistableBundle(getSettingsString(cursor)),
-                    SoundProfileHandle.NONE
-            );
-        }
-
-        private String getTempId(BiMap<Long, String> map, Cursor cursor) {
-            int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_ID);
-            Long dbId = colIndex != -1 ? cursor.getLong(colIndex) : null;
-            populateTempIdMap(map, dbId);
-            return map.getValue(dbId);
-        }
-
-        private int getType(Cursor cursor) {
-            int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_TYPE);
-            return colIndex != -1 ? cursor.getInt(colIndex) : 0;
-        }
-
-        private String getName(Cursor cursor) {
-            int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_NAME);
-            return colIndex != -1 ? cursor.getString(colIndex) : null;
-        }
-
-        private String getInputId(Cursor cursor) {
-            int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_INPUT_ID);
-            return colIndex != -1 ? cursor.getString(colIndex) : null;
-        }
-
-        private String getPackageName(Cursor cursor) {
-            int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_PACKAGE);
-            return colIndex != -1 ? cursor.getString(colIndex) : null;
-        }
-
-        private String getSettingsString(Cursor cursor) {
-            int colIndex = cursor.getColumnIndex(mMediaQualityDbHelper.SETTINGS);
-            return colIndex != -1 ? cursor.getString(colIndex) : null;
-        }
-
         private Cursor getCursorAfterQuerying(String table, String[] columns, String selection,
                 String[] selectionArgs) {
             SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase();
@@ -1448,7 +867,8 @@
             ) {
                 List<PictureProfile> pictureProfiles = new ArrayList<>();
                 while (cursor.moveToNext()) {
-                    pictureProfiles.add(convertCursorToPictureProfileWithTempId(cursor));
+                    pictureProfiles.add(MediaQualityUtils.convertCursorToPictureProfileWithTempId(
+                            cursor, mPictureProfileTempIdMap));
                 }
                 return pictureProfiles;
             }
@@ -1463,7 +883,8 @@
             ) {
                 List<SoundProfile> soundProfiles = new ArrayList<>();
                 while (cursor.moveToNext()) {
-                    soundProfiles.add(convertCursorToSoundProfileWithTempId(cursor));
+                    soundProfiles.add(MediaQualityUtils.convertCursorToSoundProfileWithTempId(
+                            cursor, mSoundProfileTempIdMap));
                 }
                 return soundProfiles;
             }
@@ -1713,7 +1134,39 @@
         @Override
         public List<ParameterCapability> getParameterCapabilities(
                 List<String> names, UserHandle user) {
-            return new ArrayList<>();
+            byte[] byteArray = MediaQualityUtils.convertParameterToByteArray(names);
+            ParamCapability[] caps = new ParamCapability[byteArray.length];
+            try {
+                mMediaQuality.getParamCaps(byteArray, caps);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to get parameter capabilities", e);
+            }
+
+            return getListParameterCapability(caps);
+        }
+
+        private List<ParameterCapability> getListParameterCapability(ParamCapability[] caps) {
+            List<ParameterCapability> pcList = new ArrayList<>();
+            for (ParamCapability pcHal : caps) {
+                String name = MediaQualityUtils.getParameterName(pcHal.name);
+                boolean isSupported = pcHal.isSupported;
+                int type = pcHal.defaultValue == null ? 0 : pcHal.defaultValue.getTag() + 1;
+                Bundle bundle = convertToCaps(pcHal.range);
+
+                pcList.add(new ParameterCapability(name, isSupported, type, bundle));
+            }
+            return pcList;
+        }
+
+        private Bundle convertToCaps(ParameterRange range) {
+            Bundle bundle = new Bundle();
+            bundle.putObject("INT_MIN_MAX", range.numRange.getIntMinMax());
+            bundle.putObject("INT_VALUES_SUPPORTED", range.numRange.getIntValuesSupported());
+            bundle.putObject("DOUBLE_MIN_MAX", range.numRange.getDoubleMinMax());
+            bundle.putObject("DOUBLE_VALUES_SUPPORTED", range.numRange.getDoubleValuesSupported());
+            bundle.putObject("LONG_MIN_MAX", range.numRange.getLongMinMax());
+            bundle.putObject("LONG_VALUES_SUPPORTED", range.numRange.getLongValuesSupported());
+            return bundle;
         }
 
         @GuardedBy("mPictureProfileLock")
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityUtils.java b/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
new file mode 100644
index 0000000..5bd4420
--- /dev/null
+++ b/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
@@ -0,0 +1,1543 @@
+/*
+ * Copyright (C) 2025 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.media.quality;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.hardware.tv.mediaquality.DolbyAudioProcessing;
+import android.hardware.tv.mediaquality.DtsVirtualX;
+import android.hardware.tv.mediaquality.ParameterName;
+import android.hardware.tv.mediaquality.PictureParameter;
+import android.hardware.tv.mediaquality.SoundParameter;
+import android.media.quality.MediaQualityContract.BaseParameters;
+import android.media.quality.MediaQualityContract.PictureQuality;
+import android.media.quality.MediaQualityContract.SoundQuality;
+import android.media.quality.PictureProfile;
+import android.media.quality.PictureProfileHandle;
+import android.media.quality.SoundProfile;
+import android.media.quality.SoundProfileHandle;
+import android.os.PersistableBundle;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * Utility class for media quality framework.
+ *
+ * @hide
+ */
+public final class MediaQualityUtils {
+
+    private static final int MAX_UUID_GENERATION_ATTEMPTS = 10;
+    private static final String TAG = "MediaQualityUtils";
+    public static final String SETTINGS = "settings";
+
+    /**
+     * Convert PictureParameter List to PersistableBundle.
+     */
+    public static PersistableBundle convertPictureParameterListToPersistableBundle(
+            PictureParameter[] parameters) {
+        PersistableBundle bundle = new PersistableBundle();
+        for (PictureParameter pp : parameters) {
+            if (pp.getBrightness() > -1) {
+                bundle.putLong(PictureQuality.PARAMETER_BRIGHTNESS, (long) pp.getBrightness());
+            }
+            if (pp.getContrast() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_CONTRAST, pp.getContrast());
+            }
+            if (pp.getSharpness() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_SHARPNESS, pp.getSharpness());
+            }
+            if (pp.getSaturation() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_SATURATION, pp.getSaturation());
+            }
+            if (pp.getHue() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_HUE, pp.getHue());
+            }
+            if (pp.getColorTunerBrightness() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS,
+                        pp.getColorTunerBrightness());
+            }
+            if (pp.getColorTunerSaturation() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION,
+                        pp.getColorTunerSaturation());
+            }
+            if (pp.getColorTunerHue() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_HUE, pp.getColorTunerHue());
+            }
+            if (pp.getColorTunerRedOffset() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET,
+                        pp.getColorTunerRedOffset());
+            }
+            if (pp.getColorTunerGreenOffset() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET,
+                        pp.getColorTunerGreenOffset());
+            }
+            if (pp.getColorTunerBlueOffset() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET,
+                        pp.getColorTunerBlueOffset());
+            }
+            if (pp.getColorTunerRedGain() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN,
+                        pp.getColorTunerRedGain());
+            }
+            if (pp.getColorTunerGreenGain() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN,
+                        pp.getColorTunerGreenGain());
+            }
+            if (pp.getColorTunerBlueGain() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN,
+                        pp.getColorTunerBlueGain());
+            }
+            if (pp.getNoiseReduction() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_NOISE_REDUCTION,
+                        pp.getNoiseReduction());
+            }
+            if (pp.getMpegNoiseReduction() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION,
+                        pp.getMpegNoiseReduction());
+            }
+            if (pp.getFleshTone() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_FLESH_TONE, pp.getFleshTone());
+            }
+            if (pp.getDeContour() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_DECONTOUR, pp.getDeContour());
+            }
+            if (pp.getDynamicLumaControl() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL,
+                        pp.getDynamicLumaControl());
+            }
+            if (pp.getColorTemperature() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TEMPERATURE,
+                        pp.getColorTemperature());
+            }
+            if (pp.getColorTemperatureRedGain() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN,
+                        pp.getColorTemperatureRedGain());
+            }
+            if (pp.getColorTemperatureGreenGain() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN,
+                        pp.getColorTemperatureGreenGain());
+            }
+            if (pp.getColorTemperatureBlueGain() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN,
+                        pp.getColorTemperatureBlueGain());
+            }
+            if (pp.getLevelRange() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_LEVEL_RANGE, pp.getLevelRange());
+            }
+            if (pp.getHdmiRgbRange() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_HDMI_RGB_RANGE, pp.getHdmiRgbRange());
+            }
+            if (pp.getColorSpace() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_SPACE, pp.getColorSpace());
+            }
+            if (pp.getPanelInitMaxLuminceNits() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS,
+                        pp.getPanelInitMaxLuminceNits());
+            }
+            if (pp.getGamma() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_GAMMA, pp.getGamma());
+            }
+            if (pp.getColorTemperatureRedOffset() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET,
+                        pp.getColorTemperatureRedOffset());
+            }
+            if (pp.getColorTemperatureGreenOffset() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET,
+                        pp.getColorTemperatureGreenOffset());
+            }
+            if (pp.getColorTemperatureBlueOffset() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET,
+                        pp.getColorTemperatureBlueOffset());
+            }
+            if (pp.getLowBlueLight() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_LOW_BLUE_LIGHT, pp.getLowBlueLight());
+            }
+            if (pp.getLdMode() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_LD_MODE, pp.getLdMode());
+            }
+            if (pp.getOsdRedGain() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_OSD_RED_GAIN, pp.getOsdRedGain());
+            }
+            if (pp.getOsdGreenGain() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_OSD_GREEN_GAIN, pp.getOsdGreenGain());
+            }
+            if (pp.getOsdBlueGain() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_OSD_BLUE_GAIN, pp.getOsdBlueGain());
+            }
+            if (pp.getOsdRedOffset() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_OSD_RED_OFFSET, pp.getOsdRedOffset());
+            }
+            if (pp.getOsdGreenOffset() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_OSD_GREEN_OFFSET,
+                        pp.getOsdGreenOffset());
+            }
+            if (pp.getOsdBlueOffset() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_OSD_BLUE_OFFSET, pp.getOsdBlueOffset());
+            }
+            if (pp.getOsdHue() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_OSD_HUE, pp.getOsdHue());
+            }
+            if (pp.getOsdSaturation() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_OSD_SATURATION, pp.getOsdSaturation());
+            }
+            if (pp.getOsdContrast() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_OSD_CONTRAST, pp.getOsdContrast());
+            }
+            if (pp.getColorTunerHueRed() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED,
+                        pp.getColorTunerHueRed());
+            }
+            if (pp.getColorTunerHueGreen() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN,
+                        pp.getColorTunerHueGreen());
+            }
+            if (pp.getColorTunerHueBlue() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE,
+                        pp.getColorTunerHueBlue());
+            }
+            if (pp.getColorTunerHueCyan() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN,
+                        pp.getColorTunerHueCyan());
+            }
+            if (pp.getColorTunerHueMagenta() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA,
+                        pp.getColorTunerHueMagenta());
+            }
+            if (pp.getColorTunerHueYellow() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW,
+                        pp.getColorTunerHueYellow());
+            }
+            if (pp.getColorTunerHueFlesh() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH,
+                        pp.getColorTunerHueFlesh());
+            }
+            if (pp.getColorTunerSaturationRed() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED,
+                        pp.getColorTunerSaturationRed());
+            }
+            if (pp.getColorTunerSaturationGreen() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN,
+                        pp.getColorTunerSaturationGreen());
+            }
+            if (pp.getColorTunerSaturationBlue() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE,
+                        pp.getColorTunerSaturationBlue());
+            }
+            if (pp.getColorTunerSaturationCyan() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN,
+                        pp.getColorTunerSaturationCyan());
+            }
+            if (pp.getColorTunerSaturationMagenta() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA,
+                        pp.getColorTunerSaturationMagenta());
+            }
+            if (pp.getColorTunerSaturationYellow() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW,
+                        pp.getColorTunerSaturationYellow());
+            }
+            if (pp.getColorTunerSaturationFlesh() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH,
+                        pp.getColorTunerSaturationFlesh());
+            }
+            if (pp.getColorTunerLuminanceRed() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED,
+                        pp.getColorTunerLuminanceRed());
+            }
+            if (pp.getColorTunerLuminanceGreen() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN,
+                        pp.getColorTunerLuminanceGreen());
+            }
+            if (pp.getColorTunerLuminanceBlue() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE,
+                        pp.getColorTunerLuminanceBlue());
+            }
+            if (pp.getColorTunerLuminanceCyan() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN,
+                        pp.getColorTunerLuminanceCyan());
+            }
+            if (pp.getColorTunerLuminanceMagenta() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA,
+                        pp.getColorTunerLuminanceMagenta());
+            }
+            if (pp.getColorTunerLuminanceYellow() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW,
+                        pp.getColorTunerLuminanceYellow());
+            }
+            if (pp.getColorTunerLuminanceFlesh() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH,
+                        pp.getColorTunerLuminanceFlesh());
+            }
+            if (pp.getPictureQualityEventType() > -1) {
+                bundle.putInt(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE,
+                        pp.getPictureQualityEventType());
+            }
+            if (pp.getFilmMode()) {
+                bundle.putBoolean(PictureQuality.PARAMETER_FILM_MODE, true);
+            }
+            if (pp.getBlueStretch()) {
+                bundle.putBoolean(PictureQuality.PARAMETER_BLUE_STRETCH, true);
+            }
+            if (pp.getColorTune()) {
+                bundle.putBoolean(PictureQuality.PARAMETER_COLOR_TUNE, true);
+            }
+            if (pp.getGlobeDimming()) {
+                bundle.putBoolean(PictureQuality.PARAMETER_GLOBAL_DIMMING, true);
+            }
+            if (pp.getAutoPictureQualityEnabled()) {
+                bundle.putBoolean(PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED, true);
+            }
+            if (pp.getAutoSuperResolutionEnabled()) {
+                bundle.putBoolean(PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED, true);
+            }
+            if (pp.getGamutMapping()) {
+                bundle.putBoolean(PictureQuality.PARAMETER_GAMUT_MAPPING, true);
+            }
+            if (pp.getPcMode()) {
+                bundle.putBoolean(PictureQuality.PARAMETER_PC_MODE, true);
+            }
+            if (pp.getLowLatency()) {
+                bundle.putBoolean(PictureQuality.PARAMETER_LOW_LATENCY, true);
+            }
+            if (pp.getVrr()) {
+                bundle.putBoolean(PictureQuality.PARAMETER_VRR, true);
+            }
+            if (pp.getCvrr()) {
+                bundle.putBoolean(PictureQuality.PARAMETER_CVRR, true);
+            }
+            if (pp.getPanelInitMaxLuminceValid()) {
+                bundle.putBoolean(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID, true);
+            }
+            if (pp.getColorTunerSwitch()) {
+                bundle.putBoolean(PictureQuality.PARAMETER_COLOR_TUNER_SWITCH, true);
+            }
+            if (pp.getElevenPointRed() != null) {
+                bundle.putIntArray(PictureQuality.PARAMETER_ELEVEN_POINT_RED,
+                        pp.getElevenPointRed());
+            }
+            if (pp.getElevenPointBlue() != null) {
+                bundle.putIntArray(PictureQuality.PARAMETER_ELEVEN_POINT_RED,
+                        pp.getElevenPointBlue());
+            }
+            if (pp.getElevenPointGreen() != null) {
+                bundle.putIntArray(PictureQuality.PARAMETER_ELEVEN_POINT_RED,
+                        pp.getElevenPointGreen());
+            }
+        }
+        return bundle;
+    }
+
+    /**
+     * Convert PersistableBundle to PictureParameter List.
+     */
+    public static PictureParameter[] convertPersistableBundleToPictureParameterList(
+            PersistableBundle params) {
+        List<PictureParameter> pictureParams = new ArrayList<>();
+        if (params.containsKey(PictureQuality.PARAMETER_BRIGHTNESS)) {
+            pictureParams.add(PictureParameter.brightness(params.getLong(
+                    PictureQuality.PARAMETER_BRIGHTNESS)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_CONTRAST)) {
+            pictureParams.add(PictureParameter.contrast(params.getInt(
+                    PictureQuality.PARAMETER_CONTRAST)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_SHARPNESS)) {
+            pictureParams.add(PictureParameter.sharpness(params.getInt(
+                    PictureQuality.PARAMETER_SHARPNESS)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_SATURATION)) {
+            pictureParams.add(PictureParameter.saturation(params.getInt(
+                    PictureQuality.PARAMETER_SATURATION)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_HUE)) {
+            pictureParams.add(PictureParameter.hue(params.getInt(
+                    PictureQuality.PARAMETER_HUE)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS)) {
+            pictureParams.add(PictureParameter.colorTunerBrightness(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION)) {
+            pictureParams.add(PictureParameter.colorTunerSaturation(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_SATURATION)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE)) {
+            pictureParams.add(PictureParameter.colorTunerHue(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_HUE)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET)) {
+            pictureParams.add(PictureParameter.colorTunerRedOffset(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET)) {
+            pictureParams.add(PictureParameter.colorTunerGreenOffset(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET)) {
+            pictureParams.add(PictureParameter.colorTunerBlueOffset(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) {
+            pictureParams.add(PictureParameter.colorTunerRedGain(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)) {
+            pictureParams.add(PictureParameter.colorTunerGreenGain(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)) {
+            pictureParams.add(PictureParameter.colorTunerBlueGain(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_NOISE_REDUCTION)) {
+            pictureParams.add(PictureParameter.noiseReduction(
+                    (byte) params.getInt(PictureQuality.PARAMETER_NOISE_REDUCTION)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)) {
+            pictureParams.add(PictureParameter.mpegNoiseReduction(
+                    (byte) params.getInt(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_FLESH_TONE)) {
+            pictureParams.add(PictureParameter.fleshTone(
+                    (byte) params.getInt(PictureQuality.PARAMETER_FLESH_TONE)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_DECONTOUR)) {
+            pictureParams.add(PictureParameter.deContour(
+                    (byte) params.getInt(PictureQuality.PARAMETER_DECONTOUR)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)) {
+            pictureParams.add(PictureParameter.dynamicLumaControl(
+                    (byte) params.getInt(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_FILM_MODE)) {
+            pictureParams.add(PictureParameter.filmMode(params.getBoolean(
+                    PictureQuality.PARAMETER_FILM_MODE)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_BLUE_STRETCH)) {
+            pictureParams.add(PictureParameter.blueStretch(params.getBoolean(
+                    PictureQuality.PARAMETER_BLUE_STRETCH)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNE)) {
+            pictureParams.add(PictureParameter.colorTune(params.getBoolean(
+                    PictureQuality.PARAMETER_COLOR_TUNE)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE)) {
+            pictureParams.add(PictureParameter.colorTemperature(
+                    (byte) params.getInt(
+                            PictureQuality.PARAMETER_COLOR_TEMPERATURE)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_GLOBAL_DIMMING)) {
+            pictureParams.add(PictureParameter.globeDimming(params.getBoolean(
+                    PictureQuality.PARAMETER_GLOBAL_DIMMING)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED)) {
+            pictureParams.add(PictureParameter.autoPictureQualityEnabled(params.getBoolean(
+                    PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)) {
+            pictureParams.add(PictureParameter.autoSuperResolutionEnabled(params.getBoolean(
+                    PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) {
+            pictureParams.add(PictureParameter.colorTemperatureRedGain(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)) {
+            pictureParams.add(PictureParameter.colorTemperatureGreenGain(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)) {
+            pictureParams.add(PictureParameter.colorTemperatureBlueGain(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_LEVEL_RANGE)) {
+            pictureParams.add(PictureParameter.levelRange(
+                    (byte) params.getInt(PictureQuality.PARAMETER_LEVEL_RANGE)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_GAMUT_MAPPING)) {
+            pictureParams.add(PictureParameter.gamutMapping(params.getBoolean(
+                    PictureQuality.PARAMETER_GAMUT_MAPPING)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_PC_MODE)) {
+            pictureParams.add(PictureParameter.pcMode(params.getBoolean(
+                    PictureQuality.PARAMETER_PC_MODE)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_LOW_LATENCY)) {
+            pictureParams.add(PictureParameter.lowLatency(params.getBoolean(
+                    PictureQuality.PARAMETER_LOW_LATENCY)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_VRR)) {
+            pictureParams.add(PictureParameter.vrr(params.getBoolean(
+                    PictureQuality.PARAMETER_VRR)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_CVRR)) {
+            pictureParams.add(PictureParameter.cvrr(params.getBoolean(
+                    PictureQuality.PARAMETER_CVRR)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_HDMI_RGB_RANGE)) {
+            pictureParams.add(PictureParameter.hdmiRgbRange(
+                    (byte) params.getInt(PictureQuality.PARAMETER_HDMI_RGB_RANGE)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_SPACE)) {
+            pictureParams.add(PictureParameter.colorSpace(
+                    (byte) params.getInt(PictureQuality.PARAMETER_COLOR_SPACE)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS)) {
+            pictureParams.add(PictureParameter.panelInitMaxLuminceNits(
+                    params.getInt(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID)) {
+            pictureParams.add(PictureParameter.panelInitMaxLuminceValid(
+                    params.getBoolean(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_GAMMA)) {
+            pictureParams.add(PictureParameter.gamma(
+                    (byte) params.getInt(PictureQuality.PARAMETER_GAMMA)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET)) {
+            pictureParams.add(PictureParameter.colorTemperatureRedOffset(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET)) {
+            pictureParams.add(PictureParameter.colorTemperatureGreenOffset(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET)) {
+            pictureParams.add(PictureParameter.colorTemperatureBlueOffset(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_RED)) {
+            pictureParams.add(PictureParameter.elevenPointRed(params.getIntArray(
+                    PictureQuality.PARAMETER_ELEVEN_POINT_RED)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_GREEN)) {
+            pictureParams.add(PictureParameter.elevenPointGreen(params.getIntArray(
+                    PictureQuality.PARAMETER_ELEVEN_POINT_GREEN)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_BLUE)) {
+            pictureParams.add(PictureParameter.elevenPointBlue(params.getIntArray(
+                    PictureQuality.PARAMETER_ELEVEN_POINT_BLUE)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)) {
+            pictureParams.add(PictureParameter.lowBlueLight(
+                    (byte) params.getInt(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_LD_MODE)) {
+            pictureParams.add(PictureParameter.LdMode(
+                    (byte) params.getInt(PictureQuality.PARAMETER_LD_MODE)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_OSD_RED_GAIN)) {
+            pictureParams.add(PictureParameter.osdRedGain(params.getInt(
+                    PictureQuality.PARAMETER_OSD_RED_GAIN)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_OSD_GREEN_GAIN)) {
+            pictureParams.add(PictureParameter.osdGreenGain(params.getInt(
+                    PictureQuality.PARAMETER_OSD_GREEN_GAIN)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_OSD_BLUE_GAIN)) {
+            pictureParams.add(PictureParameter.osdBlueGain(params.getInt(
+                    PictureQuality.PARAMETER_OSD_BLUE_GAIN)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_OSD_RED_OFFSET)) {
+            pictureParams.add(PictureParameter.osdRedOffset(params.getInt(
+                    PictureQuality.PARAMETER_OSD_RED_OFFSET)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_OSD_GREEN_OFFSET)) {
+            pictureParams.add(PictureParameter.osdGreenOffset(params.getInt(
+                    PictureQuality.PARAMETER_OSD_GREEN_OFFSET)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_OSD_BLUE_OFFSET)) {
+            pictureParams.add(PictureParameter.osdBlueOffset(params.getInt(
+                    PictureQuality.PARAMETER_OSD_BLUE_OFFSET)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_OSD_HUE)) {
+            pictureParams.add(PictureParameter.osdHue(params.getInt(
+                    PictureQuality.PARAMETER_OSD_HUE)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_OSD_SATURATION)) {
+            pictureParams.add(PictureParameter.osdSaturation(params.getInt(
+                    PictureQuality.PARAMETER_OSD_SATURATION)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_OSD_CONTRAST)) {
+            pictureParams.add(PictureParameter.osdContrast(params.getInt(
+                    PictureQuality.PARAMETER_OSD_CONTRAST)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SWITCH)) {
+            pictureParams.add(PictureParameter.colorTunerSwitch(params.getBoolean(
+                    PictureQuality.PARAMETER_COLOR_TUNER_SWITCH)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED)) {
+            pictureParams.add(PictureParameter.colorTunerHueRed(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN)) {
+            pictureParams.add(PictureParameter.colorTunerHueGreen(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE)) {
+            pictureParams.add(PictureParameter.colorTunerHueBlue(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN)) {
+            pictureParams.add(PictureParameter.colorTunerHueCyan(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA)) {
+            pictureParams.add(PictureParameter.colorTunerHueMagenta(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW)) {
+            pictureParams.add(PictureParameter.colorTunerHueYellow(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH)) {
+            pictureParams.add(PictureParameter.colorTunerHueFlesh(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED)) {
+            pictureParams.add(PictureParameter.colorTunerSaturationRed(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN)) {
+            pictureParams.add(PictureParameter.colorTunerSaturationGreen(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE)) {
+            pictureParams.add(PictureParameter.colorTunerSaturationBlue(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN)) {
+            pictureParams.add(PictureParameter.colorTunerSaturationCyan(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA)) {
+            pictureParams.add(PictureParameter.colorTunerSaturationMagenta(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW)) {
+            pictureParams.add(PictureParameter.colorTunerSaturationYellow(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH)) {
+            pictureParams.add(PictureParameter.colorTunerSaturationFlesh(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED)) {
+            pictureParams.add(PictureParameter.colorTunerLuminanceRed(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN)) {
+            pictureParams.add(PictureParameter.colorTunerLuminanceGreen(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE)) {
+            pictureParams.add(PictureParameter.colorTunerLuminanceBlue(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN)) {
+            pictureParams.add(PictureParameter.colorTunerLuminanceCyan(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA)) {
+            pictureParams.add(PictureParameter.colorTunerLuminanceMagenta(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW)) {
+            pictureParams.add(PictureParameter.colorTunerLuminanceYellow(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH)) {
+            pictureParams.add(PictureParameter.colorTunerLuminanceFlesh(params.getInt(
+                    PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH)));
+        }
+        if (params.containsKey(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE)) {
+            pictureParams.add(PictureParameter.pictureQualityEventType(
+                    (byte) params.getInt(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE)));
+        }
+        return  (PictureParameter[]) pictureParams.toArray();
+    }
+
+    /**
+     * Convert SoundParameter List to PersistableBundle.
+     */
+    public static PersistableBundle convertSoundParameterListToPersistableBundle(
+            SoundParameter[] parameters) {
+        if (parameters == null) {
+            return null;
+        }
+
+        PersistableBundle bundle = new PersistableBundle();
+        for (SoundParameter sp: parameters) {
+            if (sp.getSurroundSoundEnabled()) {
+                bundle.putBoolean(SoundQuality.PARAMETER_SURROUND_SOUND, true);
+            }
+            if (sp.getSpeakersEnabled()) {
+                bundle.putBoolean(SoundQuality.PARAMETER_SPEAKERS, true);
+            }
+            if (sp.getAutoVolumeControl()) {
+                bundle.putBoolean(SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL, true);
+            }
+            if (sp.getDtsDrc()) {
+                bundle.putBoolean(SoundQuality.PARAMETER_DTS_DRC, true);
+            }
+            if (sp.getSurroundSoundEnabled()) {
+                bundle.putBoolean(SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS, true);
+            }
+            if (sp.getEnhancedAudioReturnChannelEnabled()) {
+                bundle.putBoolean(SoundQuality.PARAMETER_EARC, true);
+            }
+            if (sp.getBalance() > -1) {
+                bundle.putInt(SoundQuality.PARAMETER_BALANCE, sp.getBalance());
+            }
+            if (sp.getBass() > -1) {
+                bundle.putInt(SoundQuality.PARAMETER_BASS, sp.getBass());
+            }
+            if (sp.getTreble() > -1) {
+                bundle.putInt(SoundQuality.PARAMETER_TREBLE, sp.getTreble());
+            }
+            if (sp.getSpeakersDelayMs() > -1) {
+                bundle.putInt(SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS,
+                        sp.getSpeakersDelayMs());
+            }
+            if (sp.getDownmixMode() > -1) {
+                bundle.putInt(SoundQuality.PARAMETER_DOWN_MIX_MODE, sp.getDownmixMode());
+            }
+            if (sp.getSoundStyle() > -1) {
+                bundle.putInt(SoundQuality.PARAMETER_SOUND_STYLE, sp.getSoundStyle());
+            }
+            if (sp.getDigitalOutput() > -1) {
+                bundle.putInt(SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE,
+                        sp.getDigitalOutput());
+            }
+            if (sp.getDolbyDialogueEnhancer() > -1) {
+                bundle.putInt(SoundQuality.PARAMETER_DIALOGUE_ENHANCER,
+                        sp.getDolbyDialogueEnhancer());
+            }
+            if (sp.getDtsVirtualX().tbHdx) {
+                bundle.putBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TBHDX, true);
+            }
+            if (sp.getDtsVirtualX().limiter) {
+                bundle.putBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_LIMITER, true);
+            }
+            if (sp.getDtsVirtualX().truSurroundX) {
+                bundle.putBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TRU_SURROUND_X, true);
+            }
+            if (sp.getDtsVirtualX().truVolumeHd) {
+                bundle.putBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TRU_VOLUME_HD, true);
+            }
+            if (sp.getDtsVirtualX().dialogClarity) {
+                bundle.putBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_DIALOG_CLARITY, true);
+            }
+            if (sp.getDtsVirtualX().definition) {
+                bundle.putBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_DEFINITION, true);
+            }
+            if (sp.getDtsVirtualX().height) {
+                bundle.putBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_HEIGHT, true);
+            }
+            if (sp.getDolbyAudioProcessing().soundMode > -1) {
+                bundle.putInt(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_SOUND_MODE,
+                        sp.getDolbyAudioProcessing().soundMode);
+            }
+            if (sp.getDolbyAudioProcessing().volumeLeveler) {
+                bundle.putBoolean(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_VOLUME_LEVELER,
+                        true);
+            }
+            if (sp.getDolbyAudioProcessing().surroundVirtualizer) {
+                bundle.putBoolean(
+                        SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_SURROUND_VIRTUALIZER,
+                        true);
+            }
+            if (sp.getDolbyAudioProcessing().dolbyAtmos) {
+                bundle.putBoolean(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_DOLBY_ATMOS,
+                        true);
+            }
+        }
+        return bundle;
+    }
+    /**
+     * Convert PersistableBundle to SoundParameter List.
+     */
+    public static SoundParameter[] convertPersistableBundleToSoundParameterList(
+            PersistableBundle params) {
+        //TODO: set EqualizerDetail
+        List<SoundParameter> soundParams = new ArrayList<>();
+        if (params.containsKey(SoundQuality.PARAMETER_BALANCE)) {
+            soundParams.add(SoundParameter.balance(params.getInt(
+                    SoundQuality.PARAMETER_BALANCE)));
+        }
+        if (params.containsKey(SoundQuality.PARAMETER_BASS)) {
+            soundParams.add(SoundParameter.bass(params.getInt(SoundQuality.PARAMETER_BASS)));
+        }
+        if (params.containsKey(SoundQuality.PARAMETER_TREBLE)) {
+            soundParams.add(SoundParameter.treble(params.getInt(
+                    SoundQuality.PARAMETER_TREBLE)));
+        }
+        if (params.containsKey(SoundQuality.PARAMETER_SURROUND_SOUND)) {
+            soundParams.add(SoundParameter.surroundSoundEnabled(params.getBoolean(
+                    SoundQuality.PARAMETER_SURROUND_SOUND)));
+        }
+        if (params.containsKey(SoundQuality.PARAMETER_SPEAKERS)) {
+            soundParams.add(SoundParameter.speakersEnabled(params.getBoolean(
+                    SoundQuality.PARAMETER_SPEAKERS)));
+        }
+        if (params.containsKey(SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS)) {
+            soundParams.add(SoundParameter.speakersDelayMs(params.getInt(
+                    SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS)));
+        }
+        if (params.containsKey(SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL)) {
+            soundParams.add(SoundParameter.autoVolumeControl(params.getBoolean(
+                    SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL)));
+        }
+        if (params.containsKey(SoundQuality.PARAMETER_DTS_DRC)) {
+            soundParams.add(SoundParameter.dtsDrc(params.getBoolean(
+                    SoundQuality.PARAMETER_DTS_DRC)));
+        }
+        if (params.containsKey(SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS)) {
+            soundParams.add(SoundParameter.surroundSoundEnabled(params.getBoolean(
+                    SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS)));
+        }
+        if (params.containsKey(SoundQuality.PARAMETER_EARC)) {
+            soundParams.add(SoundParameter.enhancedAudioReturnChannelEnabled(params.getBoolean(
+                    SoundQuality.PARAMETER_EARC)));
+        }
+        if (params.containsKey(SoundQuality.PARAMETER_DOWN_MIX_MODE)) {
+            soundParams.add(SoundParameter.downmixMode((byte) params.getInt(
+                    SoundQuality.PARAMETER_DOWN_MIX_MODE)));
+        }
+        if (params.containsKey(SoundQuality.PARAMETER_SOUND_STYLE)) {
+            soundParams.add(SoundParameter.soundStyle((byte) params.getInt(
+                    SoundQuality.PARAMETER_SOUND_STYLE)));
+        }
+        if (params.containsKey(SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE)) {
+            soundParams.add(SoundParameter.digitalOutput((byte) params.getInt(
+                    SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE)));
+        }
+        if (params.containsKey(SoundQuality.PARAMETER_DIALOGUE_ENHANCER)) {
+            soundParams.add(SoundParameter.dolbyDialogueEnhancer((byte) params.getInt(
+                    SoundQuality.PARAMETER_DIALOGUE_ENHANCER)));
+        }
+
+        DolbyAudioProcessing dab = new DolbyAudioProcessing();
+        dab.soundMode =
+                (byte) params.getInt(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_SOUND_MODE);
+        dab.volumeLeveler =
+                params.getBoolean(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_VOLUME_LEVELER);
+        dab.surroundVirtualizer = params.getBoolean(
+                SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_SURROUND_VIRTUALIZER);
+        dab.dolbyAtmos =
+                params.getBoolean(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_DOLBY_ATMOS);
+        soundParams.add(SoundParameter.dolbyAudioProcessing(dab));
+
+        DtsVirtualX dts = new DtsVirtualX();
+        dts.tbHdx = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TBHDX);
+        dts.limiter = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_LIMITER);
+        dts.truSurroundX = params.getBoolean(
+                SoundQuality.PARAMETER_DTS_VIRTUAL_X_TRU_SURROUND_X);
+        dts.truVolumeHd = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TRU_VOLUME_HD);
+        dts.dialogClarity = params.getBoolean(
+                SoundQuality.PARAMETER_DTS_VIRTUAL_X_DIALOG_CLARITY);
+        dts.definition = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_DEFINITION);
+        dts.height = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_HEIGHT);
+        soundParams.add(SoundParameter.dtsVirtualX(dts));
+
+        return  (SoundParameter[]) soundParams.toArray();
+    }
+
+    private static String persistableBundleToJson(PersistableBundle bundle) {
+        JSONObject json = new JSONObject();
+        for (String key : bundle.keySet()) {
+            Object value = bundle.get(key);
+            try {
+                if (value instanceof String) {
+                    json.put(key, bundle.getString(key));
+                } else if (value instanceof Integer) {
+                    json.put(key, bundle.getInt(key));
+                } else if (value instanceof Long) {
+                    json.put(key, bundle.getLong(key));
+                } else if (value instanceof Boolean) {
+                    json.put(key, bundle.getBoolean(key));
+                } else if (value instanceof Double) {
+                    json.put(key, bundle.getDouble(key));
+                }
+            } catch (JSONException e) {
+                Log.e(TAG, "Unable to serialize ", e);
+            }
+        }
+        return json.toString();
+    }
+
+    private static PersistableBundle jsonToPersistableBundle(String jsonString) {
+        PersistableBundle bundle = new PersistableBundle();
+        if (jsonString != null) {
+            JSONObject jsonObject = null;
+            try {
+                jsonObject = new JSONObject(jsonString);
+
+                Iterator<String> keys = jsonObject.keys();
+                while (keys.hasNext()) {
+                    String key = keys.next();
+                    Object value = jsonObject.get(key);
+
+                    if (value instanceof String) {
+                        bundle.putString(key, (String) value);
+                    } else if (value instanceof Integer) {
+                        bundle.putInt(key, (Integer) value);
+                    } else if (value instanceof Boolean) {
+                        bundle.putBoolean(key, (Boolean) value);
+                    } else if (value instanceof Double) {
+                        bundle.putDouble(key, (Double) value);
+                    } else if (value instanceof Long) {
+                        bundle.putLong(key, (Long) value);
+                    }
+                }
+            } catch (JSONException e) {
+                throw new RuntimeException(e);
+            }
+        }
+        return bundle;
+    }
+
+    /**
+     * Populates the given map with the ID and generated UUID.
+     */
+    public static void populateTempIdMap(BiMap<Long, String> map, Long id) {
+        if (id != null && map.getValue(id) == null) {
+            String uuid;
+            int attempts = 0;
+            while (attempts < MAX_UUID_GENERATION_ATTEMPTS) {
+                uuid = UUID.randomUUID().toString();
+                if (map.getKey(uuid) == null) {
+                    map.put(id, uuid);
+                    return;
+                }
+                attempts++;
+            }
+        }
+    }
+
+    /**
+     * Get Content Values.
+     */
+    public static ContentValues getContentValues(Long dbId, Integer profileType, String name,
+            String packageName, String inputId, PersistableBundle params) {
+        ContentValues values = new ContentValues();
+        if (dbId != null) {
+            values.put(BaseParameters.PARAMETER_ID, dbId);
+        }
+        if (profileType != null) {
+            values.put(BaseParameters.PARAMETER_TYPE, profileType);
+        }
+        if (name != null) {
+            values.put(BaseParameters.PARAMETER_NAME, name);
+        }
+        if (packageName != null) {
+            values.put(BaseParameters.PARAMETER_PACKAGE, packageName);
+        }
+        if (inputId != null) {
+            values.put(BaseParameters.PARAMETER_INPUT_ID, inputId);
+        }
+        if (params != null) {
+            values.put(SETTINGS, persistableBundleToJson(params));
+        }
+        return values;
+    }
+
+    /**
+     * Get Media Profile Columns.
+     */
+    public static String[] getMediaProfileColumns(boolean includeParams) {
+        ArrayList<String> columns = new ArrayList<>(Arrays.asList(
+                BaseParameters.PARAMETER_ID,
+                BaseParameters.PARAMETER_TYPE,
+                BaseParameters.PARAMETER_NAME,
+                BaseParameters.PARAMETER_INPUT_ID,
+                BaseParameters.PARAMETER_PACKAGE)
+        );
+        if (includeParams) {
+            columns.add(SETTINGS);
+        }
+        return columns.toArray(new String[0]);
+    }
+
+    /**
+     * Convert cursor to Picture Profile with temporary UUID.
+     */
+    public static PictureProfile convertCursorToPictureProfileWithTempId(Cursor cursor,
+            BiMap<Long, String> map) {
+        return new PictureProfile(
+                getTempId(map, cursor),
+                getType(cursor),
+                getName(cursor),
+                getInputId(cursor),
+                getPackageName(cursor),
+                jsonToPersistableBundle(getSettingsString(cursor)),
+                PictureProfileHandle.NONE
+        );
+    }
+
+    /**
+     * Convert cursor to Sound Profile with temporary UUID.
+     */
+    public static SoundProfile convertCursorToSoundProfileWithTempId(Cursor cursor, BiMap<Long,
+            String> map) {
+        return new SoundProfile(
+                getTempId(map, cursor),
+                getType(cursor),
+                getName(cursor),
+                getInputId(cursor),
+                getPackageName(cursor),
+                jsonToPersistableBundle(getSettingsString(cursor)),
+                SoundProfileHandle.NONE
+        );
+    }
+
+    /**
+     * Convert parameter to byte array.
+     */
+    public static byte[] convertParameterToByteArray(List<String> names) {
+        /**
+         * TODO Add following to ParameterName & add conversion here.
+         * - PICTURE_QUALITY_EVENT_TYPE
+         * - PANEL_INIT_MAX_LUMINCE_NITS
+         */
+
+        HashSet<String> nameMap = new HashSet<>(names);
+
+        List<Byte> bytes = new ArrayList<>();
+        // Picture Quality parameters
+        if (nameMap.contains(PictureQuality.PARAMETER_BRIGHTNESS)) {
+            bytes.add(ParameterName.BRIGHTNESS);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_BRIGHTNESS)) {
+            bytes.add(ParameterName.BRIGHTNESS);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_CONTRAST)) {
+            bytes.add(ParameterName.CONTRAST);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_SHARPNESS)) {
+            bytes.add(ParameterName.SHARPNESS);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_SATURATION)) {
+            bytes.add(ParameterName.SATURATION);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_HUE)) {
+            bytes.add(ParameterName.HUE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_BRIGHTNESS)) {
+            bytes.add(ParameterName.BRIGHTNESS);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS)) {
+            bytes.add(ParameterName.COLOR_TUNER_BRIGHTNESS);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_SATURATION)) {
+            bytes.add(ParameterName.SATURATION);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION)) {
+            bytes.add(ParameterName.COLOR_TUNER_SATURATION);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_HUE)) {
+            bytes.add(ParameterName.HUE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE)) {
+            bytes.add(ParameterName.COLOR_TUNER_HUE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET)) {
+            bytes.add(ParameterName.COLOR_TUNER_RED_OFFSET);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET)) {
+            bytes.add(ParameterName.COLOR_TUNER_GREEN_OFFSET);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET)) {
+            bytes.add(ParameterName.COLOR_TUNER_BLUE_OFFSET);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) {
+            bytes.add(ParameterName.COLOR_TUNER_RED_GAIN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)) {
+            bytes.add(ParameterName.COLOR_TUNER_GREEN_GAIN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)) {
+            bytes.add(ParameterName.COLOR_TUNER_BLUE_GAIN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_NOISE_REDUCTION)) {
+            bytes.add(ParameterName.NOISE_REDUCTION);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)) {
+            bytes.add(ParameterName.MPEG_NOISE_REDUCTION);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_FLESH_TONE)) {
+            bytes.add(ParameterName.FLASH_TONE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_DECONTOUR)) {
+            bytes.add(ParameterName.DE_CONTOUR);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)) {
+            bytes.add(ParameterName.DYNAMIC_LUMA_CONTROL);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_FILM_MODE)) {
+            bytes.add(ParameterName.FILM_MODE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_BLUE_STRETCH)) {
+            bytes.add(ParameterName.BLUE_STRETCH);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNE)) {
+            bytes.add(ParameterName.COLOR_TUNE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TEMPERATURE)) {
+            bytes.add(ParameterName.COLOR_TEMPERATURE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_GLOBAL_DIMMING)) {
+            bytes.add(ParameterName.GLOBE_DIMMING);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED)) {
+            bytes.add(ParameterName.AUTO_PICTUREQUALITY_ENABLED);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)) {
+            bytes.add(ParameterName.AUTO_SUPER_RESOLUTION_ENABLED);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_LEVEL_RANGE)) {
+            bytes.add(ParameterName.LEVEL_RANGE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_GAMUT_MAPPING)) {
+            bytes.add(ParameterName.GAMUT_MAPPING);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_PC_MODE)) {
+            bytes.add(ParameterName.PC_MODE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_LOW_LATENCY)) {
+            bytes.add(ParameterName.LOW_LATENCY);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_VRR)) {
+            bytes.add(ParameterName.VRR);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_CVRR)) {
+            bytes.add(ParameterName.CVRR);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_HDMI_RGB_RANGE)) {
+            bytes.add(ParameterName.HDMI_RGB_RANGE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_SPACE)) {
+            bytes.add(ParameterName.COLOR_SPACE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID)) {
+            bytes.add(ParameterName.PANEL_INIT_MAX_LUMINCE_VALID);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_GAMMA)) {
+            bytes.add(ParameterName.GAMMA);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET)) {
+            bytes.add(ParameterName.COLOR_TEMPERATURE_RED_OFFSET);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET)) {
+            bytes.add(ParameterName.COLOR_TEMPERATURE_GREEN_OFFSET);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET)) {
+            bytes.add(ParameterName.COLOR_TEMPERATURE_BLUE_OFFSET);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_ELEVEN_POINT_RED)) {
+            bytes.add(ParameterName.ELEVEN_POINT_RED);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_ELEVEN_POINT_GREEN)) {
+            bytes.add(ParameterName.ELEVEN_POINT_GREEN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_ELEVEN_POINT_BLUE)) {
+            bytes.add(ParameterName.ELEVEN_POINT_BLUE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)) {
+            bytes.add(ParameterName.LOW_BLUE_LIGHT);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_LD_MODE)) {
+            bytes.add(ParameterName.LD_MODE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_OSD_RED_GAIN)) {
+            bytes.add(ParameterName.OSD_RED_GAIN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_OSD_GREEN_GAIN)) {
+            bytes.add(ParameterName.OSD_GREEN_GAIN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_OSD_BLUE_GAIN)) {
+            bytes.add(ParameterName.OSD_BLUE_GAIN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_OSD_RED_OFFSET)) {
+            bytes.add(ParameterName.OSD_RED_OFFSET);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_OSD_GREEN_OFFSET)) {
+            bytes.add(ParameterName.OSD_GREEN_OFFSET);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_OSD_BLUE_OFFSET)) {
+            bytes.add(ParameterName.OSD_BLUE_OFFSET);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_OSD_HUE)) {
+            bytes.add(ParameterName.OSD_HUE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_OSD_SATURATION)) {
+            bytes.add(ParameterName.OSD_SATURATION);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_OSD_CONTRAST)) {
+            bytes.add(ParameterName.OSD_CONTRAST);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SWITCH)) {
+            bytes.add(ParameterName.COLOR_TUNER_SWITCH);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED)) {
+            bytes.add(ParameterName.COLOR_TUNER_HUE_RED);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN)) {
+            bytes.add(ParameterName.COLOR_TUNER_HUE_GREEN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE)) {
+            bytes.add(ParameterName.COLOR_TUNER_HUE_BLUE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN)) {
+            bytes.add(ParameterName.COLOR_TUNER_HUE_CYAN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA)) {
+            bytes.add(ParameterName.COLOR_TUNER_HUE_MAGENTA);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW)) {
+            bytes.add(ParameterName.COLOR_TUNER_HUE_YELLOW);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH)) {
+            bytes.add(ParameterName.COLOR_TUNER_HUE_FLESH);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED)) {
+            bytes.add(ParameterName.COLOR_TUNER_SATURATION_RED);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN)) {
+            bytes.add(ParameterName.COLOR_TUNER_SATURATION_GREEN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE)) {
+            bytes.add(ParameterName.COLOR_TUNER_SATURATION_BLUE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN)) {
+            bytes.add(ParameterName.COLOR_TUNER_SATURATION_CYAN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA)) {
+            bytes.add(ParameterName.COLOR_TUNER_SATURATION_MAGENTA);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW)) {
+            bytes.add(ParameterName.COLOR_TUNER_SATURATION_YELLOW);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH)) {
+            bytes.add(ParameterName.COLOR_TUNER_SATURATION_FLESH);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED)) {
+            bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_RED);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN)) {
+            bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_GREEN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE)) {
+            bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_BLUE);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN)) {
+            bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_CYAN);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA)) {
+            bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_MAGENTA);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW)) {
+            bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_YELLOW);
+        }
+        if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH)) {
+            bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_FLESH);
+        }
+
+        // Sound Quality parameters
+        if (nameMap.contains(SoundQuality.PARAMETER_BALANCE)) {
+            bytes.add(ParameterName.BALANCE);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_BASS)) {
+            bytes.add(ParameterName.BASS);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_TREBLE)) {
+            bytes.add(ParameterName.TREBLE);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_SURROUND_SOUND)) {
+            bytes.add(ParameterName.SURROUND_SOUND_ENABLED);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_EQUALIZER_DETAIL)) {
+            bytes.add(ParameterName.EQUALIZER_DETAIL);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_SPEAKERS)) {
+            bytes.add(ParameterName.SPEAKERS_ENABLED);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS)) {
+            bytes.add(ParameterName.SPEAKERS_DELAY_MS);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_EARC)) {
+            bytes.add(ParameterName.ENHANCED_AUDIO_RETURN_CHANNEL_ENABLED);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL)) {
+            bytes.add(ParameterName.AUTO_VOLUME_CONTROL);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_DOWN_MIX_MODE)) {
+            bytes.add(ParameterName.DOWNMIX_MODE);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_DTS_DRC)) {
+            bytes.add(ParameterName.DTS_DRC);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING)) {
+            bytes.add(ParameterName.DOLBY_AUDIO_PROCESSING);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_DIALOGUE_ENHANCER)) {
+            bytes.add(ParameterName.DOLBY_DIALOGUE_ENHANCER);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_DTS_VIRTUAL_X)) {
+            bytes.add(ParameterName.DTS_VIRTUAL_X);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS)) {
+            bytes.add(ParameterName.DIGITAL_OUTPUT_DELAY_MS);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE)) {
+            bytes.add(ParameterName.DIGITAL_OUTPUT);
+        }
+        if (nameMap.contains(SoundQuality.PARAMETER_SOUND_STYLE)) {
+            bytes.add(ParameterName.SOUND_STYLE);
+        }
+
+        byte[] byteArray = new byte[bytes.size()];
+        for (int i = 0; i < bytes.size(); i++) {
+            byteArray[i] = bytes.get(i);
+        }
+        return byteArray;
+    }
+
+    /**
+     * Get Parameter Name based on byte.
+     */
+    public static String getParameterName(byte pn) {
+        Map<Byte, String> parameterNameMap = new HashMap<>();
+        parameterNameMap.put(ParameterName.BRIGHTNESS, PictureQuality.PARAMETER_BRIGHTNESS);
+        parameterNameMap.put(ParameterName.CONTRAST, PictureQuality.PARAMETER_CONTRAST);
+        parameterNameMap.put(ParameterName.SHARPNESS, PictureQuality.PARAMETER_SHARPNESS);
+        parameterNameMap.put(ParameterName.SATURATION, PictureQuality.PARAMETER_SATURATION);
+        parameterNameMap.put(ParameterName.HUE, PictureQuality.PARAMETER_HUE);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_BRIGHTNESS,
+                PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION,
+                PictureQuality.PARAMETER_COLOR_TUNER_SATURATION);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_HUE,
+                PictureQuality.PARAMETER_COLOR_TUNER_HUE);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_RED_OFFSET,
+                PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_GREEN_OFFSET,
+                PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_BLUE_OFFSET,
+                PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_RED_GAIN,
+                PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_GREEN_GAIN,
+                PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_BLUE_GAIN,
+                PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN);
+        parameterNameMap.put(ParameterName.NOISE_REDUCTION,
+                PictureQuality.PARAMETER_NOISE_REDUCTION);
+        parameterNameMap.put(ParameterName.MPEG_NOISE_REDUCTION,
+                PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION);
+        parameterNameMap.put(ParameterName.FLASH_TONE, PictureQuality.PARAMETER_FLESH_TONE);
+        parameterNameMap.put(ParameterName.DE_CONTOUR, PictureQuality.PARAMETER_DECONTOUR);
+        parameterNameMap.put(ParameterName.DYNAMIC_LUMA_CONTROL,
+                PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL);
+        parameterNameMap.put(ParameterName.FILM_MODE,
+                PictureQuality.PARAMETER_FILM_MODE);
+        parameterNameMap.put(ParameterName.BLACK_STRETCH,
+                PictureQuality.PARAMETER_BLACK_STRETCH);
+        parameterNameMap.put(ParameterName.BLUE_STRETCH,
+                PictureQuality.PARAMETER_BLUE_STRETCH);
+        parameterNameMap.put(ParameterName.COLOR_TUNE,
+                PictureQuality.PARAMETER_COLOR_TUNE);
+        parameterNameMap.put(ParameterName.COLOR_TEMPERATURE,
+                PictureQuality.PARAMETER_COLOR_TEMPERATURE);
+        parameterNameMap.put(ParameterName.GLOBE_DIMMING,
+                PictureQuality.PARAMETER_GLOBAL_DIMMING);
+        parameterNameMap.put(ParameterName.AUTO_PICTUREQUALITY_ENABLED,
+                PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED);
+        parameterNameMap.put(ParameterName.AUTO_SUPER_RESOLUTION_ENABLED,
+                PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED);
+        parameterNameMap.put(ParameterName.LEVEL_RANGE, PictureQuality.PARAMETER_LEVEL_RANGE);
+        parameterNameMap.put(ParameterName.GAMUT_MAPPING,
+                PictureQuality.PARAMETER_GAMUT_MAPPING);
+        parameterNameMap.put(ParameterName.PC_MODE, PictureQuality.PARAMETER_PC_MODE);
+        parameterNameMap.put(ParameterName.LOW_LATENCY, PictureQuality.PARAMETER_LOW_LATENCY);
+        parameterNameMap.put(ParameterName.VRR, PictureQuality.PARAMETER_VRR);
+        parameterNameMap.put(ParameterName.CVRR, PictureQuality.PARAMETER_CVRR);
+        parameterNameMap.put(ParameterName.HDMI_RGB_RANGE,
+                PictureQuality.PARAMETER_HDMI_RGB_RANGE);
+        parameterNameMap.put(ParameterName.COLOR_SPACE, PictureQuality.PARAMETER_COLOR_SPACE);
+        parameterNameMap.put(ParameterName.PANEL_INIT_MAX_LUMINCE_VALID,
+                PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID);
+        parameterNameMap.put(ParameterName.GAMMA, PictureQuality.PARAMETER_GAMMA);
+        parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_RED_GAIN,
+                PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_GAIN);
+        parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_GREEN_GAIN,
+                PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_GAIN);
+        parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_BLUE_GAIN,
+                PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_GAIN);
+        parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_RED_OFFSET,
+                PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET);
+        parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_GREEN_OFFSET,
+                PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET);
+        parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_BLUE_OFFSET,
+                PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET);
+        parameterNameMap.put(ParameterName.ELEVEN_POINT_RED,
+                PictureQuality.PARAMETER_ELEVEN_POINT_RED);
+        parameterNameMap.put(ParameterName.ELEVEN_POINT_GREEN,
+                PictureQuality.PARAMETER_ELEVEN_POINT_GREEN);
+        parameterNameMap.put(ParameterName.ELEVEN_POINT_BLUE,
+                PictureQuality.PARAMETER_ELEVEN_POINT_BLUE);
+        parameterNameMap.put(ParameterName.LOW_BLUE_LIGHT,
+                PictureQuality.PARAMETER_LOW_BLUE_LIGHT);
+        parameterNameMap.put(ParameterName.LD_MODE, PictureQuality.PARAMETER_LD_MODE);
+        parameterNameMap.put(ParameterName.OSD_RED_GAIN, PictureQuality.PARAMETER_OSD_RED_GAIN);
+        parameterNameMap.put(ParameterName.OSD_GREEN_GAIN,
+                PictureQuality.PARAMETER_OSD_GREEN_GAIN);
+        parameterNameMap.put(ParameterName.OSD_BLUE_GAIN,
+                PictureQuality.PARAMETER_OSD_BLUE_GAIN);
+        parameterNameMap.put(ParameterName.OSD_RED_OFFSET,
+                PictureQuality.PARAMETER_OSD_RED_OFFSET);
+        parameterNameMap.put(ParameterName.OSD_GREEN_OFFSET,
+                PictureQuality.PARAMETER_OSD_GREEN_OFFSET);
+        parameterNameMap.put(ParameterName.OSD_BLUE_OFFSET,
+                PictureQuality.PARAMETER_OSD_BLUE_OFFSET);
+        parameterNameMap.put(ParameterName.OSD_HUE, PictureQuality.PARAMETER_OSD_HUE);
+        parameterNameMap.put(ParameterName.OSD_SATURATION,
+                PictureQuality.PARAMETER_OSD_SATURATION);
+        parameterNameMap.put(ParameterName.OSD_CONTRAST,
+                PictureQuality.PARAMETER_OSD_CONTRAST);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_SWITCH,
+                PictureQuality.PARAMETER_COLOR_TUNER_SWITCH);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_RED,
+                PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_GREEN,
+                PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_BLUE,
+                PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_CYAN,
+                PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_MAGENTA,
+                PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_YELLOW,
+                PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_FLESH,
+                PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_RED,
+                PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_GREEN,
+                PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_BLUE,
+                PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_CYAN,
+                PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_MAGENTA,
+                PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_YELLOW,
+                PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_FLESH,
+                PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_RED,
+                PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_GREEN,
+                PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_BLUE,
+                PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_CYAN,
+                PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_MAGENTA,
+                PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_YELLOW,
+                PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW);
+        parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_FLESH,
+                PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH);
+        parameterNameMap.put(ParameterName.BALANCE, SoundQuality.PARAMETER_BALANCE);
+        parameterNameMap.put(ParameterName.BASS, SoundQuality.PARAMETER_BASS);
+        parameterNameMap.put(ParameterName.TREBLE, SoundQuality.PARAMETER_TREBLE);
+        parameterNameMap.put(ParameterName.SURROUND_SOUND_ENABLED,
+                SoundQuality.PARAMETER_SURROUND_SOUND);
+        parameterNameMap.put(ParameterName.EQUALIZER_DETAIL,
+                SoundQuality.PARAMETER_EQUALIZER_DETAIL);
+        parameterNameMap.put(ParameterName.SPEAKERS_ENABLED, SoundQuality.PARAMETER_SPEAKERS);
+        parameterNameMap.put(ParameterName.SPEAKERS_DELAY_MS,
+                SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS);
+        parameterNameMap.put(ParameterName.ENHANCED_AUDIO_RETURN_CHANNEL_ENABLED,
+                SoundQuality.PARAMETER_EARC);
+        parameterNameMap.put(ParameterName.AUTO_VOLUME_CONTROL,
+                SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL);
+        parameterNameMap.put(ParameterName.DOWNMIX_MODE, SoundQuality.PARAMETER_DOWN_MIX_MODE);
+        parameterNameMap.put(ParameterName.DTS_DRC, SoundQuality.PARAMETER_DTS_DRC);
+        parameterNameMap.put(ParameterName.DOLBY_AUDIO_PROCESSING,
+                SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING);
+        parameterNameMap.put(ParameterName.DOLBY_DIALOGUE_ENHANCER,
+                SoundQuality.PARAMETER_DIALOGUE_ENHANCER);
+        parameterNameMap.put(ParameterName.DTS_VIRTUAL_X,
+                SoundQuality.PARAMETER_DTS_VIRTUAL_X);
+        parameterNameMap.put(ParameterName.DIGITAL_OUTPUT,
+                SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE);
+        parameterNameMap.put(ParameterName.DIGITAL_OUTPUT_DELAY_MS,
+                SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS);
+        parameterNameMap.put(ParameterName.SOUND_STYLE, SoundQuality.PARAMETER_SOUND_STYLE);
+
+        return parameterNameMap.get(pn);
+    }
+
+    private static String getTempId(BiMap<Long, String> map, Cursor cursor) {
+        int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_ID);
+        Long dbId = colIndex != -1 ? cursor.getLong(colIndex) : null;
+        populateTempIdMap(map, dbId);
+        return map.getValue(dbId);
+    }
+
+    private static int getType(Cursor cursor) {
+        int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_TYPE);
+        return colIndex != -1 ? cursor.getInt(colIndex) : 0;
+    }
+
+    private static String getName(Cursor cursor) {
+        int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_NAME);
+        return colIndex != -1 ? cursor.getString(colIndex) : null;
+    }
+
+    private static String getInputId(Cursor cursor) {
+        int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_INPUT_ID);
+        return colIndex != -1 ? cursor.getString(colIndex) : null;
+    }
+
+    private static String getPackageName(Cursor cursor) {
+        int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_PACKAGE);
+        return colIndex != -1 ? cursor.getString(colIndex) : null;
+    }
+
+    private static String getSettingsString(Cursor cursor) {
+        int colIndex = cursor.getColumnIndex(SETTINGS);
+        return colIndex != -1 ? cursor.getString(colIndex) : null;
+    }
+
+    private MediaQualityUtils() {
+
+    }
+}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 0ed5228..a80b1b2 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -171,11 +171,11 @@
     void onProposedRotationChanged(int displayId, int rotation, boolean isValid);
 
     /**
-     * Notifies System UI that the display is ready to show system decorations.
+     * Notifies System UI that the system decorations should be added on the display.
      *
      * @param displayId display ID
      */
-    void onDisplayReady(int displayId);
+    void onDisplayAddSystemDecorations(int displayId);
 
     /**
      * Notifies System UI that the system decorations should be removed from the display.
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index e753f27..c546388 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -87,7 +87,6 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
-import android.util.IntArray;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -125,7 +124,6 @@
 import com.android.server.power.ShutdownCheckPoints;
 import com.android.server.power.ShutdownThread;
 import com.android.server.wm.ActivityTaskManagerInternal;
-import com.android.systemui.shared.Flags;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -343,19 +341,15 @@
 
     @Override
     public void onDisplayAdded(int displayId) {
-        if (Flags.statusBarConnectedDisplays()) {
-            synchronized (mLock) {
-                mDisplayUiState.put(displayId, new UiState());
-            }
+        synchronized (mLock) {
+            mDisplayUiState.put(displayId, new UiState());
         }
     }
 
     @Override
     public void onDisplayRemoved(int displayId) {
-        if (Flags.statusBarConnectedDisplays()) {
-            synchronized (mLock) {
-                mDisplayUiState.remove(displayId);
-            }
+        synchronized (mLock) {
+            mDisplayUiState.remove(displayId);
         }
     }
 
@@ -776,10 +770,11 @@
         }
 
         @Override
-        public void onDisplayReady(int displayId) {
+        public void onDisplayAddSystemDecorations(int displayId) {
             if (isVisibleBackgroundUserOnDisplay(displayId)) {
                 if (SPEW) {
-                    Slog.d(TAG, "Skipping onDisplayReady for visible background user "
+                    Slog.d(TAG, "Skipping onDisplayAddSystemDecorations for visible background "
+                            + "user "
                             + mUserManagerInternal.getUserAssignedToDisplay(displayId));
                 }
                 return;
@@ -787,7 +782,7 @@
             IStatusBar bar = mBar;
             if (bar != null) {
                 try {
-                    bar.onDisplayReady(displayId);
+                    bar.onDisplayAddSystemDecorations(displayId);
                 } catch (RemoteException ex) {}
             }
         }
@@ -1366,66 +1361,53 @@
         return mTracingEnabled;
     }
 
+    // TODO(b/117478341): make it aware of multi-display if needed.
     @Override
     public void disable(int what, IBinder token, String pkg) {
         disableForUser(what, token, pkg, mCurrentUserId);
     }
 
-    /**
-     * Disable additional status bar features for user for all displays. Pass the bitwise-or of the
-     * {@code #DISABLE_*} flags. To re-enable everything, pass {@code #DISABLE_NONE}.
-     *
-     * Warning: Only pass {@code #DISABLE_*} flags into this function, do not use
-     * {@code #DISABLE2_*} flags.
-     */
+    // TODO(b/117478341): make it aware of multi-display if needed.
     @Override
     public void disableForUser(int what, IBinder token, String pkg, int userId) {
         enforceStatusBar();
         enforceValidCallingUser();
 
         synchronized (mLock) {
-            IntArray displayIds = new IntArray();
-            for (int i = 0; i < mDisplayUiState.size(); i++) {
-                displayIds.add(mDisplayUiState.keyAt(i));
-            }
-            disableLocked(displayIds, userId, what, token, pkg, 1);
+            disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 1);
         }
     }
 
+    // TODO(b/117478341): make it aware of multi-display if needed.
     /**
-     * Disable additional status bar features. Pass the bitwise-or of the {@code #DISABLE2_*} flags.
-     * To re-enable everything, pass {@code #DISABLE2_NONE}.
+     * Disable additional status bar features. Pass the bitwise-or of the DISABLE2_* flags.
+     * To re-enable everything, pass {@link #DISABLE2_NONE}.
      *
-     * Warning: Only pass {@code #DISABLE2_*} flags into this function, do not use
-     * {@code #DISABLE_*} flags.
+     * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
      */
     @Override
     public void disable2(int what, IBinder token, String pkg) {
         disable2ForUser(what, token, pkg, mCurrentUserId);
     }
 
+    // TODO(b/117478341): make it aware of multi-display if needed.
     /**
-     * Disable additional status bar features for a given user for all displays. Pass the bitwise-or
-     * of the {@code #DISABLE2_*} flags. To re-enable everything, pass {@code #DISABLE2_NONE}.
+     * Disable additional status bar features for a given user. Pass the bitwise-or of the
+     * DISABLE2_* flags. To re-enable everything, pass {@link #DISABLE_NONE}.
      *
-     * Warning: Only pass {@code #DISABLE2_*} flags into this function, do not use
-     * {@code #DISABLE_*}  flags.
+     * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
      */
     @Override
     public void disable2ForUser(int what, IBinder token, String pkg, int userId) {
         enforceStatusBar();
 
         synchronized (mLock) {
-            IntArray displayIds = new IntArray();
-            for (int i = 0; i < mDisplayUiState.size(); i++) {
-                displayIds.add(mDisplayUiState.keyAt(i));
-            }
-            disableLocked(displayIds, userId, what, token, pkg, 2);
+            disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 2);
         }
     }
 
-    private void disableLocked(IntArray displayIds, int userId, int what, IBinder token,
-            String pkg, int whichFlag) {
+    private void disableLocked(int displayId, int userId, int what, IBinder token, String pkg,
+            int whichFlag) {
         // It's important that the the callback and the call to mBar get done
         // in the same order when multiple threads are calling this function
         // so they are paired correctly.  The messages on the handler will be
@@ -1435,27 +1417,18 @@
         // Ensure state for the current user is applied, even if passed a non-current user.
         final int net1 = gatherDisableActionsLocked(mCurrentUserId, 1);
         final int net2 = gatherDisableActionsLocked(mCurrentUserId, 2);
-        boolean shouldCallNotificationOnSetDisabled = false;
-        IStatusBar bar = mBar;
-        for (int displayId : displayIds.toArray()) {
-            final UiState state = getUiState(displayId);
-            if (!state.disableEquals(net1, net2)) {
-                shouldCallNotificationOnSetDisabled = true;
-                state.setDisabled(net1, net2);
-                if (bar != null) {
-                    try {
-                        // TODO(b/388244660): Create IStatusBar#disableForAllDisplays to avoid
-                        // multiple IPC calls.
-                        bar.disable(displayId, net1, net2);
-                    } catch (RemoteException ex) {
-                        Slog.e(TAG, "Unable to disable Status bar.", ex);
-                    }
+        final UiState state = getUiState(displayId);
+        if (!state.disableEquals(net1, net2)) {
+            state.setDisabled(net1, net2);
+            mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1));
+            IStatusBar bar = mBar;
+            if (bar != null) {
+                try {
+                    bar.disable(displayId, net1, net2);
+                } catch (RemoteException ex) {
                 }
             }
         }
-        if (shouldCallNotificationOnSetDisabled) {
-            mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1));
-        }
     }
 
     /**
@@ -1610,8 +1583,7 @@
         if (SPEW) Slog.d(TAG, "setDisableFlags(0x" + Integer.toHexString(flags) + ")");
 
         synchronized (mLock) {
-            disableLocked(IntArray.wrap(new int[]{displayId}), mCurrentUserId, flags,
-                    mSysUiVisToken, cause, 1);
+            disableLocked(displayId, mCurrentUserId, flags, mSysUiVisToken, cause, 1);
         }
     }
 
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
index 872ab59..f413fe3 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
@@ -26,7 +26,7 @@
     /**
      * Notifies the display is ready for adding wallpaper on it.
      */
-    public abstract void onDisplayReady(int displayId);
+    public abstract void onDisplayAddSystemDecorations(int displayId);
 
     /** Notifies when display stop showing system decorations and wallpaper. */
     public abstract void onDisplayRemoveSystemDecorations(int displayId);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index db530e7..09b1073 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1636,8 +1636,8 @@
 
     private final class LocalService extends WallpaperManagerInternal {
         @Override
-        public void onDisplayReady(int displayId) {
-            onDisplayReadyInternal(displayId);
+        public void onDisplayAddSystemDecorations(int displayId) {
+            onDisplayAddSystemDecorationsInternal(displayId);
         }
 
         @Override
@@ -3944,7 +3944,7 @@
         return (wallpaper != null) ? wallpaper.allowBackup : false;
     }
 
-    private void onDisplayReadyInternal(int displayId) {
+    private void onDisplayAddSystemDecorationsInternal(int displayId) {
         synchronized (mLock) {
             if (mLastWallpaper == null) {
                 return;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index c7d4467..6f76618 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -3655,7 +3655,7 @@
     private static String getIntentRedirectPreventedLogMessage(@NonNull String message,
             @NonNull Intent intent, int intentCreatorUid, @Nullable String intentCreatorPackage,
             int callingUid, @Nullable String callingPackage) {
-        return "[IntentRedirect]" + message + " intentCreatorUid: " + intentCreatorUid
+        return "[IntentRedirect Hardening] " + message + " intentCreatorUid: " + intentCreatorUid
                 + "; intentCreatorPackage: " + intentCreatorPackage + "; callingUid: " + callingUid
                 + "; callingPackage: " + callingPackage + "; intent: " + intent;
     }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index cf111cd..ddb9f17 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2988,37 +2988,44 @@
             throw new SecurityException("Requires permission "
                     + android.Manifest.permission.DEVICE_POWER);
         }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                setLockScreenShownLocked(keyguardShowing, aodShowing);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
 
-        synchronized (mGlobalLock) {
-            final long ident = Binder.clearCallingIdentity();
-            if (mKeyguardShown != keyguardShowing) {
-                mKeyguardShown = keyguardShowing;
-                final Message msg = PooledLambda.obtainMessage(
-                        ActivityManagerInternal::reportCurKeyguardUsageEvent, mAmInternal,
-                        keyguardShowing);
-                mH.sendMessage(msg);
+    @GuardedBy("mGlobalLock")
+    void setLockScreenShownLocked(boolean keyguardShowing, boolean aodShowing) {
+        if (mKeyguardShown != keyguardShowing) {
+            mKeyguardShown = keyguardShowing;
+            final Message msg = PooledLambda.obtainMessage(
+                    ActivityManagerInternal::reportCurKeyguardUsageEvent, mAmInternal,
+                    keyguardShowing);
+            mH.sendMessage(msg);
+        }
+        // Always reset the state regardless of keyguard-showing change, because that means the
+        // unlock is either completed or canceled.
+        if ((mDemoteTopAppReasons & DEMOTE_TOP_REASON_DURING_UNLOCKING) != 0) {
+            mDemoteTopAppReasons &= ~DEMOTE_TOP_REASON_DURING_UNLOCKING;
+            // The scheduling group of top process was demoted by unlocking, so recompute
+            // to restore its real top priority if possible.
+            if (mTopApp != null) {
+                mTopApp.scheduleUpdateOomAdj();
             }
-            // Always reset the state regardless of keyguard-showing change, because that means the
-            // unlock is either completed or canceled.
-            if ((mDemoteTopAppReasons & DEMOTE_TOP_REASON_DURING_UNLOCKING) != 0) {
-                mDemoteTopAppReasons &= ~DEMOTE_TOP_REASON_DURING_UNLOCKING;
-                // The scheduling group of top process was demoted by unlocking, so recompute
-                // to restore its real top priority if possible.
-                if (mTopApp != null) {
-                    mTopApp.scheduleUpdateOomAdj();
-                }
-            }
-            try {
-                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "setLockScreenShown");
-                mRootWindowContainer.forAllDisplays(displayContent -> {
-                    mKeyguardController.setKeyguardShown(displayContent.getDisplayId(),
-                            keyguardShowing, aodShowing);
-                });
-                maybeHideLockedProfileActivityLocked();
-            } finally {
-                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-                Binder.restoreCallingIdentity(ident);
-            }
+        }
+        try {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "setLockScreenShown");
+            mRootWindowContainer.forAllDisplays(displayContent -> {
+                mKeyguardController.setKeyguardShown(displayContent.getDisplayId(),
+                        keyguardShowing, aodShowing);
+            });
+            maybeHideLockedProfileActivityLocked();
+        } finally {
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
         mH.post(() -> {
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index ab1778a..15c0789 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -295,9 +295,14 @@
                 // {@link ActivityRecord#shouldCreateAppCompatDisplayInsets()} will be false for
                 // both activities that are naturally resizeable and activities that have been
                 // forced resizeable.
+                // Camera compat mode is an exception to this, where the activity is letterboxed
+                // to an aspect ratio commonly found on phones, e.g. 16:9, to avoid issues like
+                // stretching of the camera preview.
                 || (Flags.ignoreAspectRatioRestrictionsForResizeableFreeformActivities()
                     && task.getWindowingMode() == WINDOWING_MODE_FREEFORM
-                    && !mActivityRecord.shouldCreateAppCompatDisplayInsets())) {
+                    && !mActivityRecord.shouldCreateAppCompatDisplayInsets()
+                    && !AppCompatCameraPolicy.shouldCameraCompatControlAspectRatio(
+                            mActivityRecord))) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java
index 6bf1c46..e3906f9 100644
--- a/services/core/java/com/android/server/wm/DesktopModeHelper.java
+++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java
@@ -23,6 +23,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
 
 /**
  * Constants for desktop mode feature
@@ -35,7 +36,7 @@
             "persist.wm.debug.desktop_mode_enforce_device_restrictions", true);
 
     /** Whether desktop mode is enabled. */
-    static boolean isDesktopModeEnabled() {
+    private static boolean isDesktopModeEnabled() {
         return DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue();
     }
 
@@ -56,11 +57,30 @@
         return context.getResources().getBoolean(R.bool.config_isDesktopModeSupported);
     }
 
+    static boolean isDesktopModeDevOptionsSupported(@NonNull Context context) {
+        return context.getResources().getBoolean(R.bool.config_isDesktopModeDevOptionSupported);
+    }
+
+    /**
+     * Check if Desktop mode should be enabled because the dev option is shown and enabled.
+     */
+    private static boolean isDesktopModeEnabledByDevOption(@NonNull Context context) {
+        return DesktopModeFlags.isDesktopModeForcedEnabled() && (isDesktopModeDevOptionsSupported(
+                context) || isDeviceEligibleForDesktopMode(context));
+    }
+
+    @VisibleForTesting
+    static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) {
+        return !shouldEnforceDeviceRestrictions() || isDesktopModeSupported(context)  || (
+                Flags.enableDesktopModeThroughDevOption() && isDesktopModeDevOptionsSupported(
+                        context));
+    }
+
     /**
      * Return {@code true} if desktop mode can be entered on the current device.
      */
     static boolean canEnterDesktopMode(@NonNull Context context) {
-        return isDesktopModeEnabled()
-                && (!shouldEnforceDeviceRestrictions() || isDesktopModeSupported(context));
+        return (isDesktopModeEnabled() && isDeviceEligibleForDesktopMode(context))
+                || isDesktopModeEnabledByDevOption(context);
     }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 5b16199..5329e3b 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1887,17 +1887,17 @@
         mCanSystemBarsBeShownByUser = canBeShown;
     }
 
-    void notifyDisplayReady() {
+    void notifyDisplayAddSystemDecorations() {
         mHandler.post(() -> {
             final int displayId = getDisplayId();
             StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
             if (statusBar != null) {
-                statusBar.onDisplayReady(displayId);
+                statusBar.onDisplayAddSystemDecorations(displayId);
             }
             final WallpaperManagerInternal wpMgr = LocalServices
                     .getService(WallpaperManagerInternal.class);
             if (wpMgr != null) {
-                wpMgr.onDisplayReady(displayId);
+                wpMgr.onDisplayAddSystemDecorations(displayId);
             }
         });
     }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index abd26b5..cf464c7 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2845,7 +2845,13 @@
         }
 
         startHomeOnDisplay(mCurrentUser, reason, displayContent.getDisplayId());
-        displayContent.getDisplayPolicy().notifyDisplayReady();
+        if (enableDisplayContentModeManagement()) {
+            if (displayContent.isSystemDecorationsSupported()) {
+                displayContent.getDisplayPolicy().notifyDisplayAddSystemDecorations();
+            }
+        } else {
+            displayContent.getDisplayPolicy().notifyDisplayAddSystemDecorations();
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 060f2e8..b4c2c01 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1865,7 +1865,7 @@
         if (keyguardState != null) {
             boolean keyguardShowing = keyguardState.getKeyguardShowing();
             boolean aodShowing = keyguardState.getAodShowing();
-            mService.setLockScreenShown(keyguardShowing, aodShowing);
+            mService.setLockScreenShownLocked(keyguardShowing, aodShowing);
         }
         return effects;
     }
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index e1f3f0e..b37bcc7 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -94,8 +94,6 @@
 
 namespace android {
 
-static const bool ENABLE_INPUT_FILTER_RUST = input_flags::enable_input_filter_rust_impl();
-
 // The exponent used to calculate the pointer speed scaling factor.
 // The scaling factor is calculated as 2 ^ (speed * exponent),
 // where the speed ranges from -7 to + 7 and is supplied by the user.
@@ -604,12 +602,14 @@
             return std::to_string(displayId.val());
         };
         dump += StringPrintf(INDENT "Display not interactive: %s\n",
-                             dumpSet(mLocked.nonInteractiveDisplays, streamableToString).c_str());
+                             dumpContainer(mLocked.nonInteractiveDisplays, streamableToString)
+                                     .c_str());
         dump += StringPrintf(INDENT "System UI Lights Out: %s\n",
                              toString(mLocked.systemUiLightsOut));
         dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
         dump += StringPrintf(INDENT "Display with Mouse Scaling Disabled: %s\n",
-                             dumpSet(mLocked.displaysWithMouseScalingDisabled, streamableToString)
+                             dumpContainer(mLocked.displaysWithMouseScalingDisabled,
+                                           streamableToString)
                                      .c_str());
         dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
                              toString(mLocked.pointerGesturesEnabled));
@@ -3248,27 +3248,21 @@
 static void nativeSetAccessibilityBounceKeysThreshold(JNIEnv* env, jobject nativeImplObj,
                                                       jint thresholdTimeMs) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-    if (ENABLE_INPUT_FILTER_RUST) {
-        im->getInputManager()->getInputFilter().setAccessibilityBounceKeysThreshold(
-                static_cast<nsecs_t>(thresholdTimeMs) * 1000000);
-    }
+    im->getInputManager()->getInputFilter().setAccessibilityBounceKeysThreshold(
+            static_cast<nsecs_t>(thresholdTimeMs) * 1000000);
 }
 
 static void nativeSetAccessibilitySlowKeysThreshold(JNIEnv* env, jobject nativeImplObj,
                                                     jint thresholdTimeMs) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-    if (ENABLE_INPUT_FILTER_RUST) {
-        im->getInputManager()->getInputFilter().setAccessibilitySlowKeysThreshold(
-                static_cast<nsecs_t>(thresholdTimeMs) * 1000000);
-    }
+    im->getInputManager()->getInputFilter().setAccessibilitySlowKeysThreshold(
+            static_cast<nsecs_t>(thresholdTimeMs) * 1000000);
 }
 
 static void nativeSetAccessibilityStickyKeysEnabled(JNIEnv* env, jobject nativeImplObj,
                                                     jboolean enabled) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-    if (ENABLE_INPUT_FILTER_RUST) {
-        im->getInputManager()->getInputFilter().setAccessibilityStickyKeysEnabled(enabled);
-    }
+    im->getInputManager()->getInputFilter().setAccessibilityStickyKeysEnabled(enabled);
 }
 
 static void nativeSetInputMethodConnectionIsActive(JNIEnv* env, jobject nativeImplObj,
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index b92afc5..d80fd20 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -731,7 +731,7 @@
         // WHEN display ID, 2, is ready.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
-        wallpaperManagerInternal.onDisplayReady(testDisplayId);
+        wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
 
         // Then there is a connection established for the system & lock wallpaper for display ID, 2.
         verify(mockIWallpaperService).attach(
@@ -771,7 +771,7 @@
         // WHEN display ID, 2, is ready.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
-        wallpaperManagerInternal.onDisplayReady(testDisplayId);
+        wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
 
         // Then there is a connection established for the system wallpaper for display ID, 2.
         verify(mockIWallpaperService).attach(
@@ -818,7 +818,7 @@
         // WHEN display ID, 2, is ready.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
-        wallpaperManagerInternal.onDisplayReady(testDisplayId);
+        wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
 
         // Then there is a connection established for the fallback wallpaper for display ID, 2.
         verify(mockIWallpaperService).attach(
@@ -856,7 +856,7 @@
         // GIVEN wallpaper connections have been established for display ID, 2.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
-        wallpaperManagerInternal.onDisplayReady(testDisplayId);
+        wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
         // Save displayConnector for displayId 2 before display removal.
         WallpaperManagerService.DisplayConnector displayConnector =
                 wallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
@@ -894,7 +894,7 @@
         // GIVEN wallpaper connections have been established for display ID, 2.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
-        wallpaperManagerInternal.onDisplayReady(testDisplayId);
+        wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
         // Save displayConnectors for display ID, 2, before display removal.
         WallpaperManagerService.DisplayConnector systemWallpaperDisplayConnector =
                 systemWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
@@ -930,7 +930,7 @@
         // GIVEN wallpaper connections have been established for display ID, 2.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
-        wallpaperManagerInternal.onDisplayReady(testDisplayId);
+        wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
         // Save fallback wallpaper displayConnector for display ID, 2, before display removal.
         WallpaperManagerService.DisplayConnector fallbackWallpaperConnector =
                 mService.mFallbackWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
@@ -977,7 +977,7 @@
         // GIVEN wallpaper connections have been established for displayID, 2.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
-        wallpaperManagerInternal.onDisplayReady(testDisplayId);
+        wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
         // Save displayConnector for displayId 2 before display removal.
         WallpaperManagerService.DisplayConnector displayConnector =
                 wallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
@@ -1011,7 +1011,7 @@
         // GIVEN wallpaper connections have been established for displayID, 2.
         WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
                 WallpaperManagerInternal.class);
-        wallpaperManagerInternal.onDisplayReady(testDisplayId);
+        wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
         // Save displayConnectors for displayId 2 before display removal.
         WallpaperManagerService.DisplayConnector systemWallpaperDisplayConnector =
                 systemWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 28e5be5..9cfa51a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -29,6 +29,7 @@
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
 import static android.view.accessibility.Flags.FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES;
 
+import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS;
 import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
@@ -38,6 +39,7 @@
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
 import static com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity.EXTRA_TYPE_TO_CHOOSE;
 import static com.android.server.accessibility.AccessibilityManagerService.ACTION_LAUNCH_HEARING_DEVICES_DIALOG;
+import static com.android.server.accessibility.Flags.FLAG_ENABLE_MAGNIFICATION_KEYBOARD_CONTROL;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -93,6 +95,7 @@
 import android.os.test.FakePermissionEnforcer;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
@@ -584,6 +587,31 @@
     }
 
     @Test
+    @RequiresFlagsEnabled({FLAG_ENABLE_MAGNIFICATION_KEYBOARD_CONTROL, FLAG_KEYBOARD_REPEAT_KEYS})
+    public void testRepeatKeysSettingsChanges_propagateToMagnificationController() {
+        final AccessibilityUserState userState = mA11yms.mUserStates.get(
+                mA11yms.getCurrentUserIdLocked());
+        Settings.Secure.putIntForUser(
+                mTestableContext.getContentResolver(),
+                Settings.Secure.KEY_REPEAT_ENABLED,
+                0, mA11yms.getCurrentUserIdLocked());
+
+        mA11yms.readRepeatKeysSettingsLocked(userState);
+
+        verify(mMockMagnificationController).setRepeatKeysEnabled(false);
+
+        final int timeoutMs = 42;
+        Settings.Secure.putIntForUser(
+                mTestableContext.getContentResolver(),
+                Settings.Secure.KEY_REPEAT_TIMEOUT_MS,
+                timeoutMs, mA11yms.getCurrentUserIdLocked());
+
+        mA11yms.readRepeatKeysSettingsLocked(userState);
+
+        verify(mMockMagnificationController).setRepeatKeysTimeoutMs(timeoutMs);
+    }
+
+    @Test
     public void testSettingsAlwaysOn_setEnabled_featureFlagDisabled_doNothing() {
         when(mMockMagnificationController.isAlwaysOnMagnificationFeatureFlagEnabled())
                 .thenReturn(false);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index d4f2dcc..5d94e72 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -250,9 +250,9 @@
     }
 
     @Test
-    public void sendGesture_touchableDevice_injectEvents()
-            throws RemoteException {
+    public void sendGesture_touchableDevice_injectEvents_fromAccessibilityTool() {
         when(mMockWindowManagerInternal.isTouchOrFaketouchDevice()).thenReturn(true);
+        when(mServiceInfo.isAccessibilityTool()).thenReturn(true);
         setServiceBinding(COMPONENT_NAME);
         mConnection.bindLocked();
         mConnection.onServiceConnected(COMPONENT_NAME, mMockIBinder);
@@ -263,13 +263,31 @@
         mConnection.dispatchGesture(0, parceledListSlice, Display.DEFAULT_DISPLAY);
 
         verify(mMockMotionEventInjector).injectEvents(gestureSteps, mMockServiceClient, 0,
-                Display.DEFAULT_DISPLAY);
+                Display.DEFAULT_DISPLAY, true);
+    }
+
+    @Test
+    public void sendGesture_touchableDevice_injectEvents_fromNonTool() {
+        when(mMockWindowManagerInternal.isTouchOrFaketouchDevice()).thenReturn(true);
+        when(mServiceInfo.isAccessibilityTool()).thenReturn(false);
+        setServiceBinding(COMPONENT_NAME);
+        mConnection.bindLocked();
+        mConnection.onServiceConnected(COMPONENT_NAME, mMockIBinder);
+
+        ParceledListSlice parceledListSlice = mock(ParceledListSlice.class);
+        List<GestureDescription.GestureStep> gestureSteps = mock(List.class);
+        when(parceledListSlice.getList()).thenReturn(gestureSteps);
+        mConnection.dispatchGesture(0, parceledListSlice, Display.DEFAULT_DISPLAY);
+
+        verify(mMockMotionEventInjector).injectEvents(gestureSteps, mMockServiceClient, 0,
+                Display.DEFAULT_DISPLAY, false);
     }
 
     @Test
     public void sendGesture_untouchableDevice_performGestureResultFailed()
             throws RemoteException {
         when(mMockWindowManagerInternal.isTouchOrFaketouchDevice()).thenReturn(false);
+        when(mServiceInfo.isAccessibilityTool()).thenReturn(true);
         setServiceBinding(COMPONENT_NAME);
         mConnection.bindLocked();
         mConnection.onServiceConnected(COMPONENT_NAME, mMockIBinder);
@@ -280,7 +298,7 @@
         mConnection.dispatchGesture(0, parceledListSlice, Display.DEFAULT_DISPLAY);
 
         verify(mMockMotionEventInjector, never()).injectEvents(gestureSteps, mMockServiceClient, 0,
-                Display.DEFAULT_DISPLAY);
+                Display.DEFAULT_DISPLAY, true);
         verify(mMockServiceClient).onPerformGestureResult(0, false);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
index 233caf9..d2d8c68 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
@@ -20,8 +20,7 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_HOVER_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
-import static android.view.WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY;
-import static android.view.WindowManagerPolicyConstants.FLAG_PASS_TO_USER;
+import static android.view.accessibility.Flags.FLAG_PREVENT_A11Y_NONTOOL_FROM_INJECTING_INTO_SENSITIVE_VIEWS;
 
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.anyOf;
@@ -48,10 +47,14 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.Display;
 import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
+import android.view.WindowManagerPolicyConstants;
 import android.view.accessibility.AccessibilityEvent;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -64,6 +67,7 @@
 import org.hamcrest.TypeSafeMatcher;
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -77,7 +81,7 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class MotionEventInjectorTest {
-    private static final String LOG_TAG = "MotionEventInjectorTest";
+
     private static final Matcher<MotionEvent> IS_ACTION_DOWN =
             new MotionEventActionMatcher(ACTION_DOWN);
     private static final Matcher<MotionEvent> IS_ACTION_POINTER_DOWN =
@@ -120,6 +124,9 @@
     private static final float POINTER_SIZE = 1;
     private static final int METASTATE = 0;
 
+    @Rule
+    public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     MotionEventInjector mMotionEventInjector;
     IAccessibilityServiceClient mServiceInterface;
     AccessibilityTraceManager mTrace;
@@ -201,7 +208,8 @@
         verifyNoMoreInteractions(next);
         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
 
-        final int expectedFlags = FLAG_PASS_TO_USER | FLAG_INJECTED_FROM_ACCESSIBILITY;
+        final int expectedFlags = WindowManagerPolicyConstants.FLAG_PASS_TO_USER
+                | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY;
         verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), eq(expectedFlags));
         verify(next).onMotionEvent(argThat(mIsLineStart), argThat(mIsLineStart), eq(expectedFlags));
         verifyNoMoreInteractions(next);
@@ -227,6 +235,21 @@
     }
 
     @Test
+    @EnableFlags(FLAG_PREVENT_A11Y_NONTOOL_FROM_INJECTING_INTO_SENSITIVE_VIEWS)
+    public void testInjectEvents_fromAccessibilityTool_providesToolPolicyFlag() {
+        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
+        injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE,
+                /*fromAccessibilityTool=*/true);
+
+        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
+        verify(next).onMotionEvent(
+                argThat(mIsLineStart), argThat(mIsLineStart),
+                eq(WindowManagerPolicyConstants.FLAG_PASS_TO_USER
+                        | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY
+                        | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL));
+    }
+
+    @Test
     public void testInjectEvents_gestureWithTooManyPoints_shouldNotCrash() throws  Exception {
         int tooManyPointsCount = 20;
         TouchPoint[] startTouchPoints = new TouchPoint[tooManyPointsCount];
@@ -251,14 +274,28 @@
     }
 
     @Test
-    public void testRegularEvent_afterGestureComplete_shouldPassToNext() {
+    @DisableFlags(FLAG_PREVENT_A11Y_NONTOOL_FROM_INJECTING_INTO_SENSITIVE_VIEWS)
+    public void testRegularEvent_afterGestureComplete_shouldPassToNext_withFlagInjectedFromA11y() {
         EventStreamTransformation next = attachMockNext(mMotionEventInjector);
         injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
         mMessageCapturingHandler.sendAllMessages(); // Send all motion events
         reset(next);
         mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
         verify(next).onMotionEvent(argThat(mIsClickDown), argThat(mIsClickDown),
-                eq(FLAG_INJECTED_FROM_ACCESSIBILITY));
+                eq(WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY));
+    }
+
+    @Test
+    @EnableFlags(FLAG_PREVENT_A11Y_NONTOOL_FROM_INJECTING_INTO_SENSITIVE_VIEWS)
+    public void testRegularEvent_afterGestureComplete_shouldPassToNext_withNoPolicyFlagChanges() {
+        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
+        injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
+        mMessageCapturingHandler.sendAllMessages(); // Send all motion events
+        reset(next);
+        mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
+        verify(next).onMotionEvent(argThat(mIsClickDown), argThat(mIsClickDown),
+                // The regular event passing through the filter should have no policy flag changes
+                eq(0));
     }
 
     @Test
@@ -275,7 +312,8 @@
         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
         verify(next).onMotionEvent(
                 argThat(mIsLineStart), argThat(mIsLineStart),
-                eq(FLAG_PASS_TO_USER | FLAG_INJECTED_FROM_ACCESSIBILITY));
+                eq(WindowManagerPolicyConstants.FLAG_PASS_TO_USER
+                        | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY));
     }
 
     @Test
@@ -307,10 +345,12 @@
 
         mMessageCapturingHandler.sendOneMessage(); // Send a motion event
         verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(),
-                eq(FLAG_PASS_TO_USER | FLAG_INJECTED_FROM_ACCESSIBILITY));
+                eq(WindowManagerPolicyConstants.FLAG_PASS_TO_USER
+                        | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY));
         verify(next).onMotionEvent(
                 argThat(mIsLineStart), argThat(mIsLineStart),
-                eq(FLAG_PASS_TO_USER | FLAG_INJECTED_FROM_ACCESSIBILITY));
+                eq(WindowManagerPolicyConstants.FLAG_PASS_TO_USER
+                        | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY));
     }
 
     @Test
@@ -731,8 +771,14 @@
 
     private void injectEventsSync(List<GestureStep> gestureSteps,
             IAccessibilityServiceClient serviceInterface, int sequence) {
+        injectEventsSync(gestureSteps, serviceInterface, sequence, false);
+    }
+
+    private void injectEventsSync(List<GestureStep> gestureSteps,
+            IAccessibilityServiceClient serviceInterface, int sequence,
+            boolean fromAccessibilityTool) {
         mMotionEventInjector.injectEvents(gestureSteps, serviceInterface, sequence,
-                Display.DEFAULT_DISPLAY);
+                Display.DEFAULT_DISPLAY, fromAccessibilityTool);
         // Dispatch the message sent by the injector. Our simple handler doesn't guarantee stuff
         // happens in order.
         mMessageCapturingHandler.sendLastMessage();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 3511ae1..cd6b36d 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -885,66 +885,142 @@
     }
 
     @Test
-    public void magnificationCallbacks_panMagnificationContinuous() throws RemoteException {
+    public void magnificationCallbacks_scaleMagnificationContinuous() throws RemoteException {
         setMagnificationEnabled(MODE_FULLSCREEN);
-        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 8.0f, false);
+        float currentScale = 2.0f;
+        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, currentScale, false);
         reset(mScreenMagnificationController);
 
-        DisplayMetrics metrics = new DisplayMetrics();
-        mDisplay.getMetrics(metrics);
-        float expectedStep = 27 * metrics.density;
-
         float currentCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
         float currentCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
 
-        // Start moving right using keyboard callbacks.
+        // Start zooming in using keyboard callbacks.
+        mMagnificationController.onScaleMagnificationStart(TEST_DISPLAY,
+                MagnificationController.ZOOM_DIRECTION_IN);
+
+        // The center is unchanged.
+        float newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+        float newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+        expect.that(currentCenterX).isWithin(1.0f).of(newCenterX);
+        expect.that(currentCenterY).isEqualTo(newCenterY);
+
+        // The scale is increased.
+        float newScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+        expect.that(currentScale).isLessThan(newScale);
+        currentScale = newScale;
+
+        // Wait for the initial delay to occur.
+        advanceTime(mMagnificationController.getInitialKeyboardRepeatIntervalMs() + 1);
+
+        // It should have scaled again after the handler was triggered.
+        newScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+        expect.that(currentScale).isLessThan(newScale);
+        currentScale = newScale;
+
+        for (int i = 0; i < 3; i++) {
+            // Wait for repeat delay to occur.
+            advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1);
+
+            // It should have scaled another time.
+            newScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+            expect.that(currentScale).isLessThan(newScale);
+            currentScale = newScale;
+        }
+
+        // Stop magnification scale.
+        mMagnificationController.onScaleMagnificationStop(TEST_DISPLAY,
+                MagnificationController.ZOOM_DIRECTION_IN);
+
+        // It should not scale again, even after the appropriate delay.
+        advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1);
+
+        newScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+        expect.that(currentScale).isEqualTo(newScale);
+    }
+
+    @Test
+    public void magnificationCallbacks_panMagnificationContinuous_repeatKeysTimeout200()
+            throws RemoteException {
+        // Shorter than default.
+        testMagnificationContinuousPanningWithTimeout(200);
+    }
+
+    @Test
+    public void magnificationCallbacks_panMagnificationContinuous_repeatKeysTimeout1000()
+            throws RemoteException {
+        // Longer than default.
+        testMagnificationContinuousPanningWithTimeout(1000);
+    }
+
+    @Test
+    public void magnificationCallbacks_panMagnification_notContinuousWithRepeatKeysDisabled()
+            throws RemoteException {
+        mMagnificationController.setRepeatKeysEnabled(false);
+        setMagnificationEnabled(MODE_FULLSCREEN);
+        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 4.0f, false);
+        reset(mScreenMagnificationController);
+
+        float currentCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+        float currentCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+
+        // Start moving down using keyboard callbacks.
         mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
-                MagnificationController.PAN_DIRECTION_RIGHT);
+                MagnificationController.PAN_DIRECTION_DOWN);
 
         float newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
         float newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
-        expect.that(currentCenterX).isLessThan(newCenterX);
-        expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
-        expect.that(currentCenterY).isEqualTo(newCenterY);
+        expect.that(currentCenterY).isLessThan(newCenterY);
+        expect.that(currentCenterX).isEqualTo(newCenterX);
 
         currentCenterX = newCenterX;
         currentCenterY = newCenterY;
 
-        // Wait for the initial delay to occur.
-        advanceTime(MagnificationController.INITIAL_KEYBOARD_REPEAT_INTERVAL_MS + 1);
+        for (int i = 0; i < 3; i++) {
+            // Wait for the initial delay to occur.
+            advanceTime(mMagnificationController.getInitialKeyboardRepeatIntervalMs() + 1);
 
-        // It should have moved again after the handler was triggered.
-        newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
-        newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
-        expect.that(currentCenterX).isLessThan(newCenterX);
-        expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
-        expect.that(currentCenterY).isEqualTo(newCenterY);
-        currentCenterX = newCenterX;
-        currentCenterY = newCenterY;
+            // It should not have moved again because repeat keys is disabled.
+            newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+            newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+            expect.that(currentCenterX).isEqualTo(newCenterX);
+            expect.that(currentCenterY).isEqualTo(newCenterY);
+            currentCenterX = newCenterX;
+            currentCenterY = newCenterY;
+        }
 
-        // Wait for repeat delay to occur.
-        advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1);
-
-        // It should have moved a third time.
-        newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
-        newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
-        expect.that(currentCenterX).isLessThan(newCenterX);
-        expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
-        expect.that(currentCenterY).isEqualTo(newCenterY);
-        currentCenterX = newCenterX;
-        currentCenterY = newCenterY;
-
-        // Stop magnification pan.
         mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
-                MagnificationController.PAN_DIRECTION_RIGHT);
+                MagnificationController.PAN_DIRECTION_DOWN);
+    }
 
-        // It should not move again, even after the appropriate delay.
-        advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1);
+    @Test
+    public void magnificationCallbacks_scaleMagnification_notContinuousWithRepeatKeysDisabled()
+            throws RemoteException {
+        mMagnificationController.setRepeatKeysEnabled(false);
+        setMagnificationEnabled(MODE_FULLSCREEN);
+        float currentScale = 8.0f;
+        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, currentScale, false);
+        reset(mScreenMagnificationController);
 
-        newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
-        newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
-        expect.that(newCenterX).isEqualTo(currentCenterX);
-        expect.that(newCenterY).isEqualTo(currentCenterY);
+        // Start scaling out using keyboard callbacks.
+        mMagnificationController.onScaleMagnificationStart(TEST_DISPLAY,
+                MagnificationController.ZOOM_DIRECTION_OUT);
+
+        float newScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+        expect.that(currentScale).isGreaterThan(newScale);
+
+        currentScale = newScale;
+
+        for (int i = 0; i < 3; i++) {
+            // Wait for the initial delay to occur.
+            advanceTime(mMagnificationController.getInitialKeyboardRepeatIntervalMs() + 1);
+
+            // It should not have scaled again because repeat keys is disabled.
+            newScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+            expect.that(currentScale).isEqualTo(newScale);
+        }
+
+        mMagnificationController.onScaleMagnificationStop(TEST_DISPLAY,
+                MagnificationController.ZOOM_DIRECTION_OUT);
     }
 
     @Test
@@ -1736,6 +1812,75 @@
                 MagnificationController.PAN_DIRECTION_UP);
     }
 
+    private void
+            testMagnificationContinuousPanningWithTimeout(int timeoutMs) throws RemoteException {
+        mMagnificationController.setRepeatKeysTimeoutMs(timeoutMs);
+        expect.that(timeoutMs).isEqualTo(
+                mMagnificationController.getInitialKeyboardRepeatIntervalMs());
+
+        setMagnificationEnabled(MODE_FULLSCREEN);
+        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 8.0f, false);
+        reset(mScreenMagnificationController);
+
+        DisplayMetrics metrics = new DisplayMetrics();
+        mDisplay.getMetrics(metrics);
+        float expectedStep = 27 * metrics.density;
+
+        float currentCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+        float currentCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+
+        // Start moving right using keyboard callbacks.
+        mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
+                MagnificationController.PAN_DIRECTION_RIGHT);
+
+        float newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+        float newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+        expect.that(currentCenterX).isLessThan(newCenterX);
+        expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
+        expect.that(currentCenterY).isEqualTo(newCenterY);
+
+        currentCenterX = newCenterX;
+        currentCenterY = newCenterY;
+
+        // Wait for the initial delay to occur.
+        advanceTime(timeoutMs + 1);
+
+        // It should have moved again after the handler was triggered.
+        newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+        newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+        expect.that(currentCenterX).isLessThan(newCenterX);
+        expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
+        expect.that(currentCenterY).isEqualTo(newCenterY);
+        currentCenterX = newCenterX;
+        currentCenterY = newCenterY;
+
+        for (int i = 0; i < 3; i++) {
+            // Wait for repeat delay to occur.
+            advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1);
+
+            // It should have moved another time.
+            newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+            newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+            expect.that(currentCenterX).isLessThan(newCenterX);
+            expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
+            expect.that(currentCenterY).isEqualTo(newCenterY);
+            currentCenterX = newCenterX;
+            currentCenterY = newCenterY;
+        }
+
+        // Stop magnification pan.
+        mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
+                MagnificationController.PAN_DIRECTION_RIGHT);
+
+        // It should not move again, even after the appropriate delay.
+        advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1);
+
+        newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
+        newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
+        expect.that(newCenterX).isEqualTo(currentCenterX);
+        expect.that(newCenterY).isEqualTo(currentCenterY);
+    }
+
     private void advanceTime(long timeMs) {
         mTestLooper.moveTimeForward(timeMs);
         mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
index 263ada8..148c968 100644
--- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
@@ -69,7 +69,6 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.platform.test.annotations.EnableFlags;
 import android.service.quicksettings.TileService;
 import android.testing.TestableContext;
 
@@ -80,7 +79,6 @@
 import com.android.server.LocalServices;
 import com.android.server.policy.GlobalActionsProvider;
 import com.android.server.wm.ActivityTaskManagerInternal;
-import com.android.systemui.shared.Flags;
 
 import libcore.junit.util.compat.CoreCompatChangeRule;
 
@@ -107,7 +105,6 @@
             TEST_SERVICE);
     private static final CharSequence APP_NAME = "AppName";
     private static final CharSequence TILE_LABEL = "Tile label";
-    private static final int SECONDARY_DISPLAY_ID = 2;
 
     @Rule
     public final TestableContext mContext =
@@ -752,29 +749,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
-    public void testDisableForAllDisplays() throws Exception {
-        int user1Id = 0;
-        mockUidCheck();
-        mockCurrentUserCheck(user1Id);
-
-        mStatusBarManagerService.onDisplayAdded(SECONDARY_DISPLAY_ID);
-
-        int expectedFlags = DISABLE_MASK & DISABLE_BACK;
-        String pkg = mContext.getPackageName();
-
-        // before disabling
-        assertEquals(DISABLE_NONE,
-                mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
-
-        // disable
-        mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg);
-
-        verify(mMockStatusBar).disable(0, expectedFlags, 0);
-        verify(mMockStatusBar).disable(SECONDARY_DISPLAY_ID, expectedFlags, 0);
-    }
-
-    @Test
     public void testSetHomeDisabled() throws Exception {
         int expectedFlags = DISABLE_MASK & DISABLE_HOME;
         String pkg = mContext.getPackageName();
@@ -877,29 +851,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
-    public void testDisable2ForAllDisplays() throws Exception {
-        int user1Id = 0;
-        mockUidCheck();
-        mockCurrentUserCheck(user1Id);
-
-        mStatusBarManagerService.onDisplayAdded(SECONDARY_DISPLAY_ID);
-
-        int expectedFlags = DISABLE2_MASK & DISABLE2_NOTIFICATION_SHADE;
-        String pkg = mContext.getPackageName();
-
-        // before disabling
-        assertEquals(DISABLE_NONE,
-                mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
-
-        // disable
-        mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg);
-
-        verify(mMockStatusBar).disable(0, 0, expectedFlags);
-        verify(mMockStatusBar).disable(SECONDARY_DISPLAY_ID, 0, expectedFlags);
-    }
-
-    @Test
     public void testSetQuickSettingsDisabled2() throws Exception {
         int expectedFlags = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS;
         String pkg = mContext.getPackageName();
@@ -1141,7 +1092,6 @@
         // disable
         mStatusBarManagerService.disableForUser(expectedUser1Flags, mMockStatusBar, pkg, user1Id);
         mStatusBarManagerService.disableForUser(expectedUser2Flags, mMockStatusBar, pkg, user2Id);
-
         // check that right flag is disabled
         assertEquals(expectedUser1Flags,
                 mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 839f276..19b90b6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -679,7 +679,7 @@
     }
 
     @Test
-    @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
     public void testDisallowAdjustmentType_readWriteXml_entries() throws Exception {
         int userId = ActivityManager.getCurrentUser();
 
@@ -724,7 +724,7 @@
     }
 
     @Test
-    @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    @EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
     public void testDisallowAdjustmentKeyType_readWriteXml() throws Exception {
         mAssistants.loadDefaultsFromConfig(true);
         mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index caff913..1a95984 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -604,8 +604,7 @@
 
     @Parameters(name = "{0}")
     public static List<FlagsParameterization> getParams() {
-        return FlagsParameterization.allCombinationsOf(
-            FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NM_BINDER_PERF_CACHE_CHANNELS);
+        return FlagsParameterization.allCombinationsOf();
     }
 
     public NotificationManagerServiceTest(FlagsParameterization flags) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java
new file mode 100644
index 0000000..e0b700a
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2025 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.wm;
+
+import static android.provider.Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+import android.window.DesktopModeFlags;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.R;
+import com.android.window.flags.Flags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+
+/**
+ * Test class for {@link DesktopModeHelper}.
+ */
+@SmallTest
+@Presubmit
+@EnableFlags(Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+public class DesktopModeHelperTest {
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    private Context mContext;
+    private Context mMockContext;
+    private Resources mMockResources;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        mMockContext = mock(Context.class);
+        mMockResources = mock(Resources.class);
+
+        doReturn(mMockResources).when(mMockContext).getResources();
+        doReturn(false).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
+        doReturn(false).when(mMockResources).getBoolean(
+                eq(R.bool.config_isDesktopModeDevOptionSupported));
+        doReturn(mContext.getContentResolver()).when(mMockContext).getContentResolver();
+        resetDesktopModeFlagsCache();
+        resetEnforceDeviceRestriction();
+        resetFlagOverride();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        resetDesktopModeFlagsCache();
+        resetEnforceDeviceRestriction();
+        resetFlagOverride();
+    }
+
+    @DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION})
+    @Test
+    public void canEnterDesktopMode_DWFlagDisabled_configsOff_returnsFalse() {
+        assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isFalse();
+    }
+
+    @DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION})
+    @Test
+    public void canEnterDesktopMode_DWFlagDisabled_configsOn_disableDeviceCheck_returnsFalse()
+            throws Exception {
+        doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
+        doReturn(true).when(mMockResources).getBoolean(
+                eq(R.bool.config_isDesktopModeDevOptionSupported));
+        disableEnforceDeviceRestriction();
+
+        assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isFalse();
+    }
+
+    @DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION})
+    @Test
+    public void canEnterDesktopMode_DWFlagDisabled_configDevOptionOn_returnsFalse() {
+        doReturn(true).when(mMockResources).getBoolean(
+                eq(R.bool.config_isDesktopModeDevOptionSupported));
+
+        assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isFalse();
+    }
+
+    @DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+            Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION})
+    @Test
+    public void canEnterDesktopMode_DWFlagDisabled_configDevOptionOn_flagOverrideOn_returnsTrue()
+            throws Exception {
+        doReturn(true).when(mMockResources).getBoolean(
+                eq(R.bool.config_isDesktopModeDevOptionSupported));
+        setFlagOverride(DesktopModeFlags.ToggleOverride.OVERRIDE_ON);
+
+        assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isTrue();
+    }
+
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    @Test
+    public void canEnterDesktopMode_DWFlagEnabled_configsOff_returnsFalse() {
+        assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isFalse();
+    }
+
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    @Test
+    public void canEnterDesktopMode_DWFlagEnabled_configDesktopModeOff_returnsFalse() {
+        doReturn(true).when(mMockResources).getBoolean(
+                eq(R.bool.config_isDesktopModeDevOptionSupported));
+
+        assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isFalse();
+    }
+
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    @Test
+    public void canEnterDesktopMode_DWFlagEnabled_configDesktopModeOn_returnsTrue() {
+        doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
+
+        assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isTrue();
+    }
+
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    @Test
+    public void canEnterDesktopMode_DWFlagEnabled_configsOff_disableDeviceRestrictions_returnsTrue()
+            throws Exception {
+        disableEnforceDeviceRestriction();
+
+        assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isTrue();
+    }
+
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    @Test
+    public void canEnterDesktopMode_DWFlagEnabled_configDevOptionOn_flagOverrideOn_returnsTrue() {
+        doReturn(true).when(mMockResources).getBoolean(
+                eq(R.bool.config_isDesktopModeDevOptionSupported)
+        );
+        setFlagOverride(DesktopModeFlags.ToggleOverride.OVERRIDE_ON);
+
+        assertThat(DesktopModeHelper.canEnterDesktopMode(mMockContext)).isTrue();
+    }
+
+    @Test
+    public void isDeviceEligibleForDesktopMode_configDEModeOn_returnsTrue() {
+        doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported));
+
+        assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isTrue();
+    }
+
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+    @Test
+    public void isDeviceEligibleForDesktopMode_supportFlagOff_returnsFalse() {
+        assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isFalse();
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+    @Test
+    public void isDeviceEligibleForDesktopMode_supportFlagOn_returnsFalse() {
+        assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isFalse();
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
+    @Test
+    public void isDeviceEligibleForDesktopMode_supportFlagOn_configDevOptModeOn_returnsTrue() {
+        doReturn(true).when(mMockResources).getBoolean(
+                eq(R.bool.config_isDesktopModeDevOptionSupported)
+        );
+
+        assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isTrue();
+    }
+
+    private void resetEnforceDeviceRestriction() throws Exception {
+        setEnforceDeviceRestriction(true);
+    }
+
+    private void disableEnforceDeviceRestriction() throws Exception {
+        setEnforceDeviceRestriction(false);
+    }
+
+    private void setEnforceDeviceRestriction(boolean value) throws Exception {
+        Field deviceRestriction = DesktopModeHelper.class.getDeclaredField(
+                "ENFORCE_DEVICE_RESTRICTIONS");
+        deviceRestriction.setAccessible(true);
+        deviceRestriction.setBoolean(/* obj= */ null, /* z= */ value);
+    }
+
+    private void resetDesktopModeFlagsCache() throws Exception {
+        Field cachedToggleOverride = DesktopModeFlags.class.getDeclaredField(
+                "sCachedToggleOverride");
+        cachedToggleOverride.setAccessible(true);
+        cachedToggleOverride.set(/* obj= */ null, /* value= */ null);
+    }
+
+    private void resetFlagOverride() {
+        Settings.Global.putString(mContext.getContentResolver(),
+                DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, null);
+    }
+
+    private void setFlagOverride(DesktopModeFlags.ToggleOverride override) {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, override.getSetting());
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 7af4ede..c3aa289 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4953,7 +4953,8 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    @EnableFlags({Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES,
+            Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING})
     public void testCameraCompatAspectRatioAppliedForFixedOrientationCameraActivities() {
         // Needed to create camera compat policy in DisplayContent.
         allowDesktopMode();
@@ -4965,7 +4966,8 @@
         setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);
 
         // Create task on test display.
-        final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+        final Task task = new TaskBuilder(mSupervisor).setDisplay(display)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
 
         // Create fixed portrait activity.
         final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm)
@@ -4978,7 +4980,8 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    @EnableFlags({Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES,
+            Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING})
     public void testCameraCompatAspectRatioForFixedOrientationCameraActivitiesPortraitWindow() {
         // Needed to create camera compat policy in DisplayContent.
         allowDesktopMode();
@@ -4990,7 +4993,8 @@
         setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);
 
         // Create task on test display.
-        final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+        final Task task = new TaskBuilder(mSupervisor).setDisplay(display)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
 
         // Create fixed portrait activity.
         final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm)
@@ -5003,7 +5007,8 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    @EnableFlags({Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES,
+            Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING})
     public void testCameraCompatAspectRatioAppliedInsteadOfDefaultAspectRatio() {
         // Needed to create camera compat policy in DisplayContent.
         allowDesktopMode();
@@ -5015,7 +5020,8 @@
         setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);
 
         // Create task on test display.
-        final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+        final Task task = new TaskBuilder(mSupervisor).setDisplay(display)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
 
         // App's target min aspect ratio - this should not be used, as camera controls aspect ratio.
         final float targetMinAspectRatio = 4.0f;
@@ -5032,7 +5038,8 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    @EnableFlags({Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES,
+            Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING})
     public void testCameraCompatAspectRatio_defaultAspectRatioAppliedWhenGreater() {
         // Needed to create camera compat policy in DisplayContent.
         allowDesktopMode();
@@ -5044,7 +5051,8 @@
         setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);
 
         // Create task on test display.
-        final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+        final Task task = new TaskBuilder(mSupervisor).setDisplay(display)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
 
         // App's target min aspect ratio bigger than camera compat aspect ratio - use that instead.
         final float targetMinAspectRatio = 6.0f;
diff --git a/tests/CompanionDeviceMultiDeviceTests/host/Android.bp b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp
index a0e0477..1fb18a6 100644
--- a/tests/CompanionDeviceMultiDeviceTests/host/Android.bp
+++ b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp
@@ -39,13 +39,4 @@
     device_common_data: [
         ":cdm_snippet_legacy",
     ],
-    version: {
-        py2: {
-            enabled: false,
-        },
-        py3: {
-            enabled: true,
-            embedded_launcher: true,
-        },
-    },
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 03d6ce8..18f44dd 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -51,8 +51,8 @@
             instrumentation.targetContext.contentResolver,
             Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY,
             0,
-            UserHandle.USER_CURRENT
-        );
+            UserHandle.USER_CURRENT_OR_SELF
+        )
     }
 
     private val logTag = this::class.java.simpleName
diff --git a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
index 1c2a053..c2f9adf 100644
--- a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
+++ b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
@@ -50,10 +50,7 @@
  */
 @Presubmit
 @RunWith(MockitoJUnitRunner::class)
-@EnableFlags(
-    com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
-    com.android.input.flags.Flags.FLAG_ENABLE_INPUT_FILTER_RUST_IMPL,
-)
+@EnableFlags(com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG)
 class StickyModifierStateListenerTest {
 
     @get:Rule
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
index 83d22d9..61fa7b5 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -18,18 +18,24 @@
 
 import static org.junit.Assert.assertTrue;
 
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.os.Message;
 import android.os.MessageQueue;
 import android.os.SystemClock;
+import android.os.TestLooperManager;
 import android.util.Log;
 
+import androidx.test.InstrumentationRegistry;
+
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.ArrayDeque;
+import java.util.Queue;
 import java.util.concurrent.Executor;
 
 /**
@@ -44,7 +50,9 @@
  *     The Robolectric class also allows advancing time.
  */
 public class TestLooper {
-    protected final Looper mLooper;
+    private final Looper mLooper;
+    private final TestLooperManager mTestLooperManager;
+    private final Clock mClock;
 
     private static final Constructor<Looper> LOOPER_CONSTRUCTOR;
     private static final Field THREAD_LOCAL_LOOPER_FIELD;
@@ -54,24 +62,37 @@
     private static final Method MESSAGE_MARK_IN_USE_METHOD;
     private static final String TAG = "TestLooper";
 
-    private final Clock mClock;
-
     private AutoDispatchThread mAutoDispatchThread;
 
+    /**
+     * Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection.
+     */
+    private static boolean isAtLeastBaklava() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA;
+    }
+
     static {
         try {
             LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE);
             LOOPER_CONSTRUCTOR.setAccessible(true);
             THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal");
             THREAD_LOCAL_LOOPER_FIELD.setAccessible(true);
-            MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
-            MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
-            MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
-            MESSAGE_NEXT_FIELD.setAccessible(true);
-            MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
-            MESSAGE_WHEN_FIELD.setAccessible(true);
-            MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse");
-            MESSAGE_MARK_IN_USE_METHOD.setAccessible(true);
+
+            if (isAtLeastBaklava()) {
+                MESSAGE_QUEUE_MESSAGES_FIELD = null;
+                MESSAGE_NEXT_FIELD = null;
+                MESSAGE_WHEN_FIELD = null;
+                MESSAGE_MARK_IN_USE_METHOD = null;
+            } else {
+                MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
+                MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
+                MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
+                MESSAGE_NEXT_FIELD.setAccessible(true);
+                MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
+                MESSAGE_WHEN_FIELD.setAccessible(true);
+                MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse");
+                MESSAGE_MARK_IN_USE_METHOD.setAccessible(true);
+            }
         } catch (NoSuchFieldException | NoSuchMethodException e) {
             throw new RuntimeException("Failed to initialize TestLooper", e);
         }
@@ -106,6 +127,13 @@
             throw new RuntimeException("Reflection error constructing or accessing looper", e);
         }
 
+        if (isAtLeastBaklava()) {
+            mTestLooperManager =
+                InstrumentationRegistry.getInstrumentation().acquireLooperManager(mLooper);
+        } else {
+            mTestLooperManager = null;
+        }
+
         mClock = clock;
     }
 
@@ -117,19 +145,72 @@
         return new HandlerExecutor(new Handler(getLooper()));
     }
 
-    private Message getMessageLinkedList() {
+    private Message getMessageLinkedListLegacy() {
         try {
             MessageQueue queue = mLooper.getQueue();
             return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue);
         } catch (IllegalAccessException e) {
             throw new RuntimeException("Access failed in TestLooper: get - MessageQueue.mMessages",
-                    e);
+                e);
         }
     }
 
     public void moveTimeForward(long milliSeconds) {
+        if (isAtLeastBaklava()) {
+            moveTimeForwardBaklava(milliSeconds);
+        } else {
+            moveTimeForwardLegacy(milliSeconds);
+        }
+    }
+
+    private void moveTimeForwardBaklava(long milliSeconds) {
+        // Drain all Messages from the queue.
+        Queue<Message> messages = new ArrayDeque<>();
+        while (true) {
+            Message message = mTestLooperManager.poll();
+            if (message == null) {
+                break;
+            }
+
+            // Adjust the Message's delivery time.
+            long newWhen = message.when - milliSeconds;
+            if (newWhen < 0) {
+                newWhen = 0;
+            }
+            message.when = newWhen;
+            messages.add(message);
+        }
+
+        // Repost all Messages back to the queuewith a new time.
+        while (true) {
+            Message message = messages.poll();
+            if (message == null) {
+                break;
+            }
+
+            Runnable callback = message.getCallback();
+            Handler handler = message.getTarget();
+            long when = message.getWhen();
+
+            // The Message cannot be re-enqueued because it is marked in use.
+            // Make a copy of the Message and recycle the original.
+            // This resets {@link Message#isInUse()} but retains all other content.
+            {
+                Message newMessage = Message.obtain();
+                newMessage.copyFrom(message);
+                newMessage.setCallback(callback);
+                mTestLooperManager.recycle(message);
+                message = newMessage;
+            }
+
+            // Send the Message back to its Handler to be re-enqueued.
+            handler.sendMessageAtTime(message, when);
+        }
+    }
+
+    private void moveTimeForwardLegacy(long milliSeconds) {
         try {
-            Message msg = getMessageLinkedList();
+            Message msg = getMessageLinkedListLegacy();
             while (msg != null) {
                 long updatedWhen = msg.getWhen() - milliSeconds;
                 if (updatedWhen < 0) {
@@ -147,12 +228,12 @@
         return mClock.uptimeMillis();
     }
 
-    private Message messageQueueNext() {
+    private Message messageQueueNextLegacy() {
         try {
             long now = currentTime();
 
             Message prevMsg = null;
-            Message msg = getMessageLinkedList();
+            Message msg = getMessageLinkedListLegacy();
             if (msg != null && msg.getTarget() == null) {
                 // Stalled by a barrier. Find the next asynchronous message in
                 // the queue.
@@ -185,18 +266,46 @@
     /**
      * @return true if there are pending messages in the message queue
      */
-    public synchronized boolean isIdle() {
-        Message messageList = getMessageLinkedList();
+    public boolean isIdle() {
+        if (isAtLeastBaklava()) {
+            return isIdleBaklava();
+        } else {
+            return isIdleLegacy();
+        }
+    }
 
+    private boolean isIdleBaklava() {
+        Long when = mTestLooperManager.peekWhen();
+        return when != null && currentTime() >= when;
+    }
+
+    private synchronized boolean isIdleLegacy() {
+        Message messageList = getMessageLinkedListLegacy();
         return messageList != null && currentTime() >= messageList.getWhen();
     }
 
     /**
      * @return the next message in the Looper's message queue or null if there is none
      */
-    public synchronized Message nextMessage() {
+    public Message nextMessage() {
+        if (isAtLeastBaklava()) {
+            return nextMessageBaklava();
+        } else {
+            return nextMessageLegacy();
+        }
+    }
+
+    private Message nextMessageBaklava() {
         if (isIdle()) {
-            return messageQueueNext();
+            return mTestLooperManager.poll();
+        } else {
+            return null;
+        }
+    }
+
+    private synchronized Message nextMessageLegacy() {
+        if (isIdle()) {
+            return messageQueueNextLegacy();
         } else {
             return null;
         }
@@ -206,9 +315,26 @@
      * Dispatch the next message in the queue
      * Asserts that there is a message in the queue
      */
-    public synchronized void dispatchNext() {
+    public void dispatchNext() {
+        if (isAtLeastBaklava()) {
+            dispatchNextBaklava();
+        } else {
+            dispatchNextLegacy();
+        }
+    }
+
+    private void dispatchNextBaklava() {
         assertTrue(isIdle());
-        Message msg = messageQueueNext();
+        Message msg = mTestLooperManager.poll();
+        if (msg == null) {
+            return;
+        }
+        msg.getTarget().dispatchMessage(msg);
+    }
+
+    private synchronized void dispatchNextLegacy() {
+        assertTrue(isIdle());
+        Message msg = messageQueueNextLegacy();
         if (msg == null) {
             return;
         }