Merge "Guard against crashes in effects services" into main
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
index 459c286..515ddc8 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
@@ -19,16 +19,13 @@
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.perftests.utils.ShellHelper;
-import android.util.Log;
 
 import java.util.ArrayList;
 
 // Based on //platform/frameworks/base/apct-tests/perftests/utils/BenchmarkState.java
 public class BenchmarkRunner {
-    private static final String TAG = BenchmarkRunner.class.getSimpleName();
+
     private static final long COOL_OFF_PERIOD_MS = 1000;
-    private static final int CPU_IDLE_TIMEOUT_MS = 60 * 1000;
-    private static final int CPU_IDLE_THRESHOLD_PERCENTAGE = 90;
 
     private static final int NUM_ITERATIONS = 4;
 
@@ -83,7 +80,7 @@
 
     private void prepareForNextRun() {
         SystemClock.sleep(COOL_OFF_PERIOD_MS);
-        waitCoolDownPeriod();
+        ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers");
         mStartTimeNs = System.nanoTime();
         mPausedDurationNs = 0;
     }
@@ -105,7 +102,7 @@
      * to avoid unnecessary waiting.
      */
     public void resumeTiming() {
-        waitCoolDownPeriod();
+        ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers");
         resumeTimer();
     }
 
@@ -165,56 +162,4 @@
         }
         return null;
     }
-
-    /** Waits for the broadcast queue and the CPU cores to be idle. */
-    public void waitCoolDownPeriod() {
-        waitForBroadcastIdle();
-        waitForCpuIdle();
-    }
-
-    private void waitForBroadcastIdle() {
-        Log.d(TAG, "starting to waitForBroadcastIdle");
-        final long startedAt = System.currentTimeMillis();
-        ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers");
-        final long elapsed = System.currentTimeMillis() - startedAt;
-        Log.d(TAG, "waitForBroadcastIdle is complete in " + elapsed + " ms");
-    }
-    private void waitForCpuIdle() {
-        Log.d(TAG, "starting to waitForCpuIdle");
-        final long startedAt = System.currentTimeMillis();
-        while (true) {
-            final int idleCpuPercentage = getIdleCpuPercentage();
-            final long elapsed = System.currentTimeMillis() - startedAt;
-            Log.d(TAG, "waitForCpuIdle " + idleCpuPercentage + "% (" + elapsed + "ms elapsed)");
-            if (idleCpuPercentage >= CPU_IDLE_THRESHOLD_PERCENTAGE) {
-                Log.d(TAG, "waitForCpuIdle is complete in " + elapsed + " ms");
-                return;
-            }
-            if (elapsed >= CPU_IDLE_TIMEOUT_MS) {
-                Log.e(TAG, "Ending waitForCpuIdle because it didn't finish in "
-                        + CPU_IDLE_TIMEOUT_MS + " ms");
-                return;
-            }
-            SystemClock.sleep(1000);
-        }
-    }
-
-    private int getIdleCpuPercentage() {
-        String output = ShellHelper.runShellCommand("top -m 1 -n 1");
-        String[] tokens = output.split("\\s+");
-        float totalCpu = -1;
-        float idleCpu = -1;
-        for (String token : tokens) {
-            if (token.contains("%cpu")) {
-                totalCpu = Float.parseFloat(token.split("%")[0]);
-            } else if (token.contains("%idle")) {
-                idleCpu = Float.parseFloat(token.split("%")[0]);
-            }
-        }
-        if (totalCpu < 0 || idleCpu < 0) {
-            Log.e(TAG, "Could not get idle cpu percentage, output=" + output);
-            return -1;
-        }
-        return (int) (100 * idleCpu / totalCpu);
-    }
 }
\ No newline at end of file
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 98ab0c2..762e2af 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -188,6 +188,21 @@
         }
     }
 
+    /** Tests creating a new user, with wait times between iterations. */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void createUser_realistic() throws RemoteException {
+        while (mRunner.keepRunning()) {
+            Log.i(TAG, "Starting timer");
+            final int userId = createUserNoFlags();
+
+            mRunner.pauseTiming();
+            Log.i(TAG, "Stopping timer");
+            removeUser(userId);
+            waitCoolDownPeriod();
+            mRunner.resumeTimingForNextIteration();
+        }
+    }
+
     /** Tests creating and starting a new user. */
     @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
     public void createAndStartUser() throws RemoteException {
@@ -224,6 +239,7 @@
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
             removeUser(userId);
+            waitCoolDownPeriod();
             mRunner.resumeTimingForNextIteration();
         }
     }
@@ -238,6 +254,7 @@
             mRunner.pauseTiming();
             final int userId = createUserNoFlags();
 
+            waitForBroadcastIdle();
             runThenWaitForBroadcasts(userId, () -> {
                 mRunner.resumeTiming();
                 Log.i(TAG, "Starting timer");
@@ -292,6 +309,9 @@
 
             preStartUser(userId, numberOfIterationsToSkip);
 
+            waitForBroadcastIdle();
+            waitCoolDownPeriod();
+
             runThenWaitForBroadcasts(userId, () -> {
                 mRunner.resumeTiming();
                 Log.i(TAG, "Starting timer");
@@ -333,6 +353,9 @@
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
 
+            waitForBroadcastIdle();
+            waitCoolDownPeriod();
+
             runThenWaitForBroadcasts(userId, () -> {
                 mRunner.resumeTiming();
                 Log.i(TAG, "Starting timer");
@@ -397,6 +420,7 @@
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
 
+            waitCoolDownPeriod();
             mRunner.resumeTiming();
             Log.i(TAG, "Starting timer");
 
@@ -430,6 +454,7 @@
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
             removeUser(userId);
+            waitCoolDownPeriod();
             mRunner.resumeTimingForNextIteration();
         }
     }
@@ -441,7 +466,6 @@
             mRunner.pauseTiming();
             final int startUser = mAm.getCurrentUser();
             final int userId = createUserNoFlags();
-
             mRunner.resumeTiming();
             Log.i(TAG, "Starting timer");
 
@@ -455,6 +479,27 @@
         }
     }
 
+    /** Tests switching to an uninitialized user with wait times between iterations. */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void switchUser_realistic() throws Exception {
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            final int startUser = ActivityManager.getCurrentUser();
+            final int userId = createUserNoFlags();
+            waitCoolDownPeriod();
+            Log.d(TAG, "Starting timer");
+            mRunner.resumeTiming();
+
+            switchUser(userId);
+
+            mRunner.pauseTiming();
+            Log.d(TAG, "Stopping timer");
+            switchUserNoCheck(startUser);
+            removeUser(userId);
+            mRunner.resumeTimingForNextIteration();
+        }
+    }
+
     /** Tests switching to a previously-started, but no-longer-running, user. */
     @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
     public void switchUser_stopped() throws RemoteException {
@@ -462,7 +507,6 @@
             mRunner.pauseTiming();
             final int startUser = mAm.getCurrentUser();
             final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ true);
-
             mRunner.resumeTiming();
             Log.i(TAG, "Starting timer");
 
@@ -492,6 +536,7 @@
 
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
+            waitCoolDownPeriod();
             Log.d(TAG, "Starting timer");
             mRunner.resumeTiming();
 
@@ -517,6 +562,7 @@
                 /* useStaticWallpaper */true);
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
+            waitCoolDownPeriod();
             Log.d(TAG, "Starting timer");
             mRunner.resumeTiming();
 
@@ -560,6 +606,7 @@
         final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ false);
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
+            waitCoolDownPeriod();
             Log.d(TAG, "Starting timer");
             mRunner.resumeTiming();
 
@@ -567,6 +614,7 @@
 
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
+            waitForBroadcastIdle();
             switchUserNoCheck(startUser);
             mRunner.resumeTimingForNextIteration();
         }
@@ -583,6 +631,7 @@
                 /* useStaticWallpaper */ true);
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
+            waitCoolDownPeriod();
             Log.d(TAG, "Starting timer");
             mRunner.resumeTiming();
 
@@ -590,6 +639,7 @@
 
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
+            waitForBroadcastIdle();
             switchUserNoCheck(startUser);
             mRunner.resumeTimingForNextIteration();
         }
@@ -625,11 +675,13 @@
     @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
     public void stopUser_realistic() throws RemoteException {
         final int userId = createUserNoFlags();
+        waitCoolDownPeriod();
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
             runThenWaitForBroadcasts(userId, ()-> {
                 mIam.startUserInBackground(userId);
             }, Intent.ACTION_USER_STARTED, Intent.ACTION_MEDIA_MOUNTED);
+            waitCoolDownPeriod();
             Log.d(TAG, "Starting timer");
             mRunner.resumeTiming();
 
@@ -651,7 +703,7 @@
             final int startUser = mAm.getCurrentUser();
             final int userId = createUserNoFlags();
 
-            mRunner.waitCoolDownPeriod();
+            waitForBroadcastIdle();
             mUserSwitchWaiter.runThenWaitUntilBootCompleted(userId, () -> {
                 mRunner.resumeTiming();
                 Log.i(TAG, "Starting timer");
@@ -674,7 +726,7 @@
             final int startUser = ActivityManager.getCurrentUser();
             final int userId = createUserNoFlags();
 
-            mRunner.waitCoolDownPeriod();
+            waitCoolDownPeriod();
             mUserSwitchWaiter.runThenWaitUntilBootCompleted(userId, () -> {
                 mRunner.resumeTiming();
                 Log.d(TAG, "Starting timer");
@@ -700,7 +752,7 @@
                 switchUser(userId);
             }, Intent.ACTION_MEDIA_MOUNTED);
 
-            mRunner.waitCoolDownPeriod();
+            waitForBroadcastIdle();
             mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(startUser, () -> {
                 runThenWaitForBroadcasts(userId, () -> {
                     mRunner.resumeTiming();
@@ -729,7 +781,7 @@
                 switchUser(userId);
             }, Intent.ACTION_MEDIA_MOUNTED);
 
-            mRunner.waitCoolDownPeriod();
+            waitCoolDownPeriod();
             mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(startUser, () -> {
                 runThenWaitForBroadcasts(userId, () -> {
                     mRunner.resumeTiming();
@@ -775,6 +827,7 @@
             Log.d(TAG, "Stopping timer");
             attestTrue("Failed creating profile " + userId, mUm.isManagedProfile(userId));
             removeUser(userId);
+            waitCoolDownPeriod();
             mRunner.resumeTimingForNextIteration();
         }
     }
@@ -815,6 +868,7 @@
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
             removeUser(userId);
+            waitCoolDownPeriod();
             mRunner.resumeTimingForNextIteration();
         }
     }
@@ -859,6 +913,7 @@
 
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
+            waitCoolDownPeriod();
             mRunner.resumeTimingForNextIteration();
         }
         removeUser(userId);
@@ -910,6 +965,7 @@
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
             removeUser(userId);
+            waitCoolDownPeriod();
             mRunner.resumeTimingForNextIteration();
         }
     }
@@ -974,6 +1030,7 @@
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
             removeUser(userId);
+            waitCoolDownPeriod();
             mRunner.resumeTimingForNextIteration();
         }
     }
@@ -1014,6 +1071,7 @@
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
             removeUser(userId);
+            waitCoolDownPeriod();
             mRunner.resumeTimingForNextIteration();
         }
     }
@@ -1066,6 +1124,7 @@
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
             removeUser(userId);
+            waitCoolDownPeriod();
             mRunner.resumeTimingForNextIteration();
         }
     }
@@ -1105,6 +1164,7 @@
             runThenWaitForBroadcasts(userId, () -> {
                 startUserInBackgroundAndWaitForUnlock(userId);
             }, Intent.ACTION_MEDIA_MOUNTED);
+            waitCoolDownPeriod();
             mRunner.resumeTiming();
             Log.d(TAG, "Starting timer");
 
@@ -1220,7 +1280,6 @@
      * If lack of success should fail the test, use {@link #switchUser(int)} instead.
      */
     private boolean switchUserNoCheck(int userId) throws RemoteException {
-        mRunner.waitCoolDownPeriod();
         final boolean[] success = {true};
         mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(userId, () -> {
             mAm.switchUser(userId);
@@ -1237,7 +1296,7 @@
      */
     private void stopUserAfterWaitingForBroadcastIdle(int userId)
             throws RemoteException {
-        mRunner.waitCoolDownPeriod();
+        waitForBroadcastIdle();
         stopUser(userId);
     }
 
@@ -1379,8 +1438,6 @@
      */
     private void runThenWaitForBroadcasts(int userId, FunctionalUtils.ThrowingRunnable runnable,
             String... actions) {
-        mRunner.waitCoolDownPeriod();
-
         final String unreceivedAction =
                 mBroadcastWaiter.runThenWaitForBroadcasts(userId, runnable, actions);
 
@@ -1481,4 +1538,28 @@
         assertEquals("", ShellHelper.runShellCommand("setprop " + name + " " + value));
         return TextUtils.firstNotEmpty(oldValue, "invalid");
     }
+
+    private void waitForBroadcastIdle() {
+        try {
+            ShellHelper.runShellCommandWithTimeout(
+                    "am wait-for-broadcast-idle --flush-broadcast-loopers", TIMEOUT_IN_SECOND);
+        } catch (TimeoutException e) {
+            Log.e(TAG, "Ending waitForBroadcastIdle because it is taking too long", e);
+        }
+    }
+
+    private void sleep(long ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException e) {
+            // Ignore
+        }
+    }
+
+    private void waitCoolDownPeriod() {
+        // Heuristic value based on local tests. Stability increased compared to no waiting.
+        final int tenSeconds = 1000 * 10;
+        waitForBroadcastIdle();
+        sleep(tenSeconds);
+    }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 44444b5..67752f2 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -62,6 +62,7 @@
 import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED;
 import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED;
 import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
+import static android.app.admin.flags.Flags.onboardingConsentlessBugreports;
 import static android.app.admin.flags.Flags.FLAG_IS_MTE_POLICY_ENFORCED;
 import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
 import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
@@ -17626,6 +17627,17 @@
         return onboardingBugreportV2Enabled();
     }
 
+    // TODO(b/308755220): Remove once the build is finalised.
+    /**
+     * Returns true if the flag for consentless bugreports is enabled.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean isOnboardingConsentlessBugreportFlagEnabled() {
+        return onboardingConsentlessBugreports();
+    }
+
     /**
      * Returns the subscription ids of all subscriptions which were downloaded by the calling
      * admin.
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index e2a131c..5668c54 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -140,6 +140,16 @@
   }
 }
 
+flag {
+    name: "fix_avatar_concurrent_file_write"
+    namespace: "multiuser"
+    description: "Fix potential unexpected behavior due to concurrent file writing"
+    bug: "339351031"
+    metadata {
+    	purpose: PURPOSE_BUGFIX
+  }
+}
+
 # This flag guards the private space feature and all its implementations excluding the APIs. APIs are guarded by android.os.Flags.allow_private_profile.
 flag {
     name: "enable_private_space_features"
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 9eabc8d..53771e3 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -963,11 +963,10 @@
      * originate from the system, just that we were unable to verify it. This can
      * happen for a number of reasons during normal operation.
      *
-     * @param event The {@link android.view.InputEvent} to check
+     * @param event The {@link android.view.InputEvent} to check.
      *
      * @return {@link android.view.VerifiedInputEvent}, which is a subset of the provided
-     * {@link android.view.InputEvent}
-     *         {@code null} if the event could not be verified.
+     *     {@link android.view.InputEvent}, or {@code null} if the event could not be verified.
      */
     @Nullable
     public VerifiedInputEvent verifyInputEvent(@NonNull InputEvent event) {
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index ba1915c..15b0c13 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -2263,6 +2263,7 @@
             this(modeId, width, height, refreshRate, vsyncRate, false, alternativeRefreshRates,
                     supportedHdrTypes);
         }
+
         /**
          * @hide
          */
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index da86e2d..8b9d876 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -213,7 +213,7 @@
 
     /**
      * The supported modes that will be exposed externally.
-     * Might have different set of modes that supportedModes for VRR displays
+     * Might have different set of modes than supportedModes for VRR displays
      */
     public Display.Mode[] appsSupportedModes = Display.Mode.EMPTY_ARRAY;
 
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 6db40bf..79a9f2d 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -682,7 +682,8 @@
      * <li>For a touch screen or touch pad, reports the approximate size of the contact area in
      * relation to the maximum detectable size for the device.  The value is normalized
      * to a range from 0 (smallest detectable size) to 1 (largest detectable size),
-     * although it is not a linear scale.  This value is of limited use.
+     * although it is not a linear scale.  The value of size can be used to
+     * determine fat touch events.
      * To obtain calibrated size information, use
      * {@link #AXIS_TOUCH_MAJOR} or {@link #AXIS_TOOL_MAJOR}.
      * </ul>
@@ -2795,13 +2796,8 @@
     }
 
     /**
-     * Returns the current pressure of this event for the given pointer
-     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
-     * identifier for this index).
-     * The pressure generally
-     * ranges from 0 (no pressure at all) to 1 (normal pressure), however
-     * values higher than 1 may be generated depending on the calibration of
-     * the input device.
+     * Returns the value of {@link #AXIS_PRESSURE} for the given pointer <em>index</em>.
+     *
      * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
      * (the first pointer that is down) to {@link #getPointerCount()}-1.
      *
@@ -2812,14 +2808,8 @@
     }
 
     /**
-     * Returns a scaled value of the approximate size for the given pointer
-     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
-     * identifier for this index).
-     * This represents some approximation of the area of the screen being
-     * pressed; the actual value in pixels corresponding to the
-     * touch is normalized with the device specific range of values
-     * and scaled to a value between 0 and 1. The value of size can be used to
-     * determine fat touch events.
+     * Returns the value of {@link #AXIS_SIZE} for the given pointer <em>index</em>.
+     *
      * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
      * (the first pointer that is down) to {@link #getPointerCount()}-1.
      *
@@ -2830,10 +2820,8 @@
     }
 
     /**
-     * Returns the length of the major axis of an ellipse that describes the touch
-     * area at the point of contact for the given pointer
-     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
-     * identifier for this index).
+     * Returns the value of {@link #AXIS_TOUCH_MAJOR} for the given pointer <em>index</em>.
+     *
      * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
      * (the first pointer that is down) to {@link #getPointerCount()}-1.
      *
@@ -2844,10 +2832,8 @@
     }
 
     /**
-     * Returns the length of the minor axis of an ellipse that describes the touch
-     * area at the point of contact for the given pointer
-     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
-     * identifier for this index).
+     * Returns the value of {@link #AXIS_TOUCH_MINOR} for the given pointer <em>index</em>.
+     *
      * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
      * (the first pointer that is down) to {@link #getPointerCount()}-1.
      *
@@ -2858,12 +2844,8 @@
     }
 
     /**
-     * Returns the length of the major axis of an ellipse that describes the size of
-     * the approaching tool for the given pointer
-     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
-     * identifier for this index).
-     * The tool area represents the estimated size of the finger or pen that is
-     * touching the device independent of its actual touch area at the point of contact.
+     * Returns the value of {@link #AXIS_TOOL_MAJOR} for the given pointer <em>index</em>.
+     *
      * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
      * (the first pointer that is down) to {@link #getPointerCount()}-1.
      *
@@ -2874,12 +2856,8 @@
     }
 
     /**
-     * Returns the length of the minor axis of an ellipse that describes the size of
-     * the approaching tool for the given pointer
-     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
-     * identifier for this index).
-     * The tool area represents the estimated size of the finger or pen that is
-     * touching the device independent of its actual touch area at the point of contact.
+     * Returns the value of {@link #AXIS_TOOL_MINOR} for the given pointer <em>index</em>.
+     *
      * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
      * (the first pointer that is down) to {@link #getPointerCount()}-1.
      *
@@ -2890,15 +2868,8 @@
     }
 
     /**
-     * Returns the orientation of the touch area and tool area in radians clockwise from vertical
-     * for the given pointer <em>index</em> (use {@link #getPointerId(int)} to find the pointer
-     * identifier for this index).
-     * An angle of 0 radians indicates that the major axis of contact is oriented
-     * upwards, is perfectly circular or is of unknown orientation.  A positive angle
-     * indicates that the major axis of contact is oriented to the right.  A negative angle
-     * indicates that the major axis of contact is oriented to the left.
-     * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
-     * (finger pointing fully right).
+     * Returns the value of {@link #AXIS_ORIENTATION} for the given pointer <em>index</em>.
+     *
      * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
      * (the first pointer that is down) to {@link #getPointerCount()}-1.
      *
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 075ee1b..5e3f09a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1984,9 +1984,25 @@
     public @interface ContentSensitivity {}
 
     /**
-     * Automatically determine whether a view displays sensitive content. For example, available
-     * autofill hints (or some other signal) can be used to determine if this view
-     * displays sensitive content.
+     * Content sensitivity is determined by the framework. The framework uses a heuristic to
+     * determine if this view displays sensitive content.
+     * Autofill hints i.e. {@link #getAutofillHints()}  are used in the heuristic
+     * to determine if this view should be considered as a sensitive view.
+     * <p>
+     * {@link #AUTOFILL_HINT_USERNAME},
+     * {@link #AUTOFILL_HINT_PASSWORD},
+     * {@link #AUTOFILL_HINT_CREDIT_CARD_NUMBER},
+     * {@link #AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE},
+     * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE},
+     * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY},
+     * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH},
+     * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR}
+     * are considered sensitive hints by the framework, and the list may include more hints
+     * in the future.
+     *
+     * <p> The window hosting a sensitive view will be marked as secure during an active media
+     * projection session. This would be equivalent to applying
+     * {@link android.view.WindowManager.LayoutParams#FLAG_SECURE} to the window.
      *
      * @see #getContentSensitivity()
      */
@@ -1996,6 +2012,10 @@
     /**
      * The view displays sensitive content.
      *
+     * <p> The window hosting a sensitive view will be marked as secure during an active media
+     * projection session. This would be equivalent to applying
+     * {@link android.view.WindowManager.LayoutParams#FLAG_SECURE} to the window.
+     *
      * @see #getContentSensitivity()
      */
     @FlaggedApi(FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API)
@@ -10548,9 +10568,13 @@
 
     /**
      * Sets content sensitivity mode to determine whether this view displays sensitive content
-     * (e.g. username, password etc.). The system may improve user privacy i.e. hide content
+     * (e.g. username, password etc.). The system will improve user privacy i.e. hide content
      * drawn by a sensitive view from screen sharing and recording.
      *
+     * <p> The window hosting a sensitive view will be marked as secure during an active media
+     * projection session. This would be equivalent to applying
+     * {@link android.view.WindowManager.LayoutParams#FLAG_SECURE} to the window.
+     *
      * @param mode {@link #CONTENT_SENSITIVITY_AUTO}, {@link #CONTENT_SENSITIVITY_NOT_SENSITIVE}
      *                                            or {@link #CONTENT_SENSITIVITY_SENSITIVE}
      */
@@ -10574,8 +10598,7 @@
      * {@link #setContentSensitivity(int)}.
      */
     @FlaggedApi(FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API)
-    public @ContentSensitivity
-    final int getContentSensitivity() {
+    public @ContentSensitivity final int getContentSensitivity() {
         return (mPrivateFlags4 & PFLAG4_CONTENT_SENSITIVITY_MASK)
                 >> PFLAG4_CONTENT_SENSITIVITY_SHIFT;
     }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 0f54940b..42bf420 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2801,6 +2801,10 @@
          * it from appearing in screenshots or from being viewed on non-secure
          * displays.
          *
+         * <p>See {@link android.view.View#setContentSensitivity(int)}, a window hosting
+         * a sensitive view will be marked as secure during media projection, preventing
+         * it from being viewed on non-secure displays and during screen share.
+         *
          * <p>See {@link android.view.Display#FLAG_SECURE} for more details about
          * secure surfaces and secure displays.
          */
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
index ded142c..d8f1309 100644
--- a/core/java/com/android/internal/compat/ChangeReporter.java
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -98,7 +98,7 @@
     public void reportChange(int uid, long changeId, int state, boolean isLoggableBySdk) {
         boolean isAlreadyReported =
                 checkAndSetIsAlreadyReported(uid, new ChangeReport(changeId, state));
-        if (!isAlreadyReported) {
+        if (shouldWriteToStatsLog(isAlreadyReported)) {
             FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid,
                     changeId, state, mSource);
         }
@@ -136,14 +136,16 @@
     /**
      * Returns whether the next report should be logged to FrameworkStatsLog.
      *
-     * @param uid      affected by the change
-     * @param changeId the reported change id
-     * @param state    of the reported change - enabled/disabled/only logged
+     * @param isAlreadyReported is the change already reported
      * @return true if the report should be logged
      */
     @VisibleForTesting
-    boolean shouldWriteToStatsLog(int uid, long changeId, int state) {
-        return !isAlreadyReported(uid, new ChangeReport(changeId, state));
+    boolean shouldWriteToStatsLog(boolean isAlreadyReported) {
+        // We don't log for system server
+        if (mSource == SOURCE_SYSTEM_SERVER) return false;
+
+        // Don't log if already reported
+        return !isAlreadyReported;
     }
 
     /**
@@ -160,8 +162,6 @@
             boolean isAlreadyReported, int state, boolean isLoggableBySdk) {
         // If log all bit is on, always return true.
         if (mDebugLogAll) return true;
-        // If the change has already been reported, do not write.
-        if (isAlreadyReported) return false;
 
         // If the flag is turned off or the TAG's logging is forced to debug level with
         // `adb setprop log.tag.CompatChangeReporter=DEBUG`, write to debug since the above checks
@@ -224,6 +224,19 @@
         return mReportedChanges.getOrDefault(uid, EMPTY_SET).contains(report);
     }
 
+    /**
+     * Returns whether the next report should be logged.
+     *
+     * @param uid      affected by the change
+     * @param changeId the reported change id
+     * @param state    of the reported change - enabled/disabled/only logged
+     * @return true if the report should be logged
+     */
+    @VisibleForTesting
+    boolean isAlreadyReported(int uid, long changeId, int state) {
+        return isAlreadyReported(uid, new ChangeReport(changeId, state));
+    }
+
     private void markAsReported(int uid, ChangeReport report) {
         mReportedChanges.computeIfAbsent(uid, NEW_CHANGE_REPORT_SET).add(report);
     }
diff --git a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
index 12a42f9..c32fd45 100644
--- a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
+++ b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
@@ -31,21 +31,21 @@
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Test
-    public void testStatsLogOnce() {
+    public void testIsAlreadyReportedOnce() {
         ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
         int myUid = 1022, otherUid = 1023;
         long myChangeId = 500L, otherChangeId = 600L;
         int myState = ChangeReporter.STATE_ENABLED, otherState = ChangeReporter.STATE_DISABLED;
 
-        assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState));
+        assertFalse(reporter.isAlreadyReported(myUid, myChangeId, myState));
         reporter.reportChange(myUid, myChangeId, myState);
 
         // Same report will not be logged again.
-        assertFalse(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState));
+        assertTrue(reporter.isAlreadyReported(myUid, myChangeId, myState));
         // Other reports will be logged.
-        assertTrue(reporter.shouldWriteToStatsLog(otherUid, myChangeId, myState));
-        assertTrue(reporter.shouldWriteToStatsLog(myUid, otherChangeId, myState));
-        assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, otherState));
+        assertFalse(reporter.isAlreadyReported(otherUid, myChangeId, myState));
+        assertFalse(reporter.isAlreadyReported(myUid, otherChangeId, myState));
+        assertFalse(reporter.isAlreadyReported(myUid, myChangeId, otherState));
     }
 
     @Test
@@ -55,15 +55,15 @@
         long myChangeId = 500L;
         int myState = ChangeReporter.STATE_ENABLED;
 
-        assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState));
+        assertFalse(reporter.isAlreadyReported(myUid, myChangeId, myState));
         reporter.reportChange(myUid, myChangeId, myState);
 
         // Same report will not be logged again.
-        assertFalse(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState));
+        assertTrue(reporter.isAlreadyReported(myUid, myChangeId, myState));
         reporter.resetReportedChanges(myUid);
 
         // Same report will be logged again after reset.
-        assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState));
+        assertFalse(reporter.isAlreadyReported(myUid, myChangeId, myState));
     }
 
     @Test
@@ -196,4 +196,21 @@
         // off.
         assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, false));
     }
+
+    @Test
+    public void testDontLogSystemServer() {
+        ChangeReporter systemServerReporter =
+                new ChangeReporter(ChangeReporter.SOURCE_SYSTEM_SERVER);
+
+        // We never log from system server, even if already reported.
+        assertFalse(systemServerReporter.shouldWriteToStatsLog(false));
+        assertFalse(systemServerReporter.shouldWriteToStatsLog(true));
+
+        ChangeReporter unknownSourceReporter =
+                new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
+
+        // Otherwise we log if it's not already been reported.
+        assertTrue(unknownSourceReporter.shouldWriteToStatsLog(false));
+        assertFalse(unknownSourceReporter.shouldWriteToStatsLog(true));
+    }
 }
diff --git a/core/tests/coretests/src/android/util/BinaryXmlTest.java b/core/tests/coretests/src/android/util/BinaryXmlTest.java
index 025e831..da29828 100644
--- a/core/tests/coretests/src/android/util/BinaryXmlTest.java
+++ b/core/tests/coretests/src/android/util/BinaryXmlTest.java
@@ -24,6 +24,8 @@
 import static android.util.XmlTest.doVerifyWrite;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
 import android.os.PersistableBundle;
@@ -41,12 +43,15 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
 
 @RunWith(AndroidJUnit4.class)
 public class BinaryXmlTest {
+    private static final int MAX_UNSIGNED_SHORT = 65_535;
+
     /**
      * Verify that we can write and read large numbers of interned
      * {@link String} values.
@@ -170,4 +175,49 @@
             }
         }
     }
+
+    @Test
+    public void testAttributeBytes_BinaryDataOverflow() throws Exception {
+        final TypedXmlSerializer out = Xml.newBinarySerializer();
+        final ByteArrayOutputStream os = new ByteArrayOutputStream();
+        out.setOutput(os, StandardCharsets.UTF_8.name());
+
+        final byte[] testBytes = new byte[MAX_UNSIGNED_SHORT + 1];
+        assertThrows(IOException.class,
+                () -> out.attributeBytesHex(/* namespace */ null, /* name */ "attributeBytesHex",
+                        testBytes));
+
+        assertThrows(IOException.class,
+                () -> out.attributeBytesBase64(/* namespace */ null, /* name */
+                        "attributeBytesBase64", testBytes));
+    }
+
+    @Test
+    public void testAttributeBytesHex_MaximumBinaryData() throws Exception {
+        final TypedXmlSerializer out = Xml.newBinarySerializer();
+        final ByteArrayOutputStream os = new ByteArrayOutputStream();
+        out.setOutput(os, StandardCharsets.UTF_8.name());
+
+        final byte[] testBytes = new byte[MAX_UNSIGNED_SHORT];
+        try {
+            out.attributeBytesHex(/* namespace */ null, /* name */ "attributeBytesHex", testBytes);
+        } catch (Exception e) {
+            fail("testAttributeBytesHex fails with exception: " + e.toString());
+        }
+    }
+
+    @Test
+    public void testAttributeBytesBase64_MaximumBinaryData() throws Exception {
+        final TypedXmlSerializer out = Xml.newBinarySerializer();
+        final ByteArrayOutputStream os = new ByteArrayOutputStream();
+        out.setOutput(os, StandardCharsets.UTF_8.name());
+
+        final byte[] testBytes = new byte[MAX_UNSIGNED_SHORT];
+        try {
+            out.attributeBytesBase64(/* namespace */ null, /* name */ "attributeBytesBase64",
+                    testBytes);
+        } catch (Exception e) {
+            fail("testAttributeBytesBase64 fails with exception: " + e.toString());
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index ee740fb..9114c7a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -27,6 +27,7 @@
 import android.graphics.Rect
 import android.graphics.RectF
 import android.os.RemoteException
+import android.util.TimeUtils
 import android.view.Choreographer
 import android.view.Display
 import android.view.IRemoteAnimationFinishedCallback
@@ -109,6 +110,7 @@
     private val postCommitFlingSpring = SpringForce(SPRING_SCALE)
             .setStiffness(SpringForce.STIFFNESS_LOW)
             .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+    protected var gestureProgress = 0f
 
     /** Background color to be used during the animation, also see [getBackgroundColor] */
     protected var customizedBackgroundColor = 0
@@ -212,6 +214,7 @@
 
     private fun onGestureProgress(backEvent: BackEvent) {
         val progress = gestureInterpolator.getInterpolation(backEvent.progress)
+        gestureProgress = progress
         currentClosingRect.setInterpolatedRectF(startClosingRect, targetClosingRect, progress)
         val yOffset = getYOffset(currentClosingRect, backEvent.touchY)
         currentClosingRect.offset(0f, yOffset)
@@ -257,12 +260,16 @@
         }
 
         // kick off spring animation with the current velocity from the pre-commit phase, this
-        // affects the scaling of the closing activity during post-commit
+        // affects the scaling of the closing and/or opening activity during post-commit
+        val startVelocity =
+            if (gestureProgress < 0.1f) -DEFAULT_FLING_VELOCITY else -velocity * SPRING_SCALE
         val flingAnimation = SpringAnimation(postCommitFlingScale, SPRING_SCALE)
-            .setStartVelocity(min(0f, -velocity * SPRING_SCALE))
+            .setStartVelocity(startVelocity.coerceIn(-MAX_FLING_VELOCITY, 0f))
             .setStartValue(SPRING_SCALE)
             .setSpring(postCommitFlingSpring)
         flingAnimation.start()
+        // do an animation-frame immediately to prevent idle frame
+        flingAnimation.doAnimationFrame(choreographer.lastFrameTimeNanos / TimeUtils.NANOS_PER_MS)
 
         val valueAnimator =
             ValueAnimator.ofFloat(1f, 0f).setDuration(getPostCommitAnimationDuration())
@@ -292,6 +299,7 @@
         enteringTarget?.let {
             if (it.leash != null && it.leash.isValid) {
                 transaction.setCornerRadius(it.leash, 0f)
+                if (!triggerBack) transaction.setAlpha(it.leash, 0f)
                 it.leash.release()
             }
             enteringTarget = null
@@ -315,19 +323,22 @@
         isLetterboxed = false
         enteringHasSameLetterbox = false
         lastPostCommitFlingScale = SPRING_SCALE
+        gestureProgress = 0f
     }
 
     protected fun applyTransform(
         leash: SurfaceControl?,
         rect: RectF,
         alpha: Float,
-        baseTransformation: Transformation? = null
+        baseTransformation: Transformation? = null,
+        flingMode: FlingMode = FlingMode.NO_FLING
     ) {
         if (leash == null || !leash.isValid) return
         tempRectF.set(rect)
-        if (leash == closingTarget?.leash) {
-            lastPostCommitFlingScale = (postCommitFlingScale.value / SPRING_SCALE).coerceIn(
-                    minimumValue = MAX_FLING_SCALE, maximumValue = lastPostCommitFlingScale
+        if (flingMode != FlingMode.NO_FLING) {
+            lastPostCommitFlingScale = min(
+                postCommitFlingScale.value / SPRING_SCALE,
+                if (flingMode == FlingMode.FLING_BOUNCE) 1f else lastPostCommitFlingScale
             )
             // apply an additional scale to the closing target to account for fling velocity
             tempRectF.scaleCentered(lastPostCommitFlingScale)
@@ -529,14 +540,30 @@
         private const val MAX_SCRIM_ALPHA_DARK = 0.8f
         private const val MAX_SCRIM_ALPHA_LIGHT = 0.2f
         private const val SPRING_SCALE = 100f
-        private const val MAX_FLING_SCALE = 0.6f
+        private const val MAX_FLING_VELOCITY = 1000f
+        private const val DEFAULT_FLING_VELOCITY = 120f
+    }
+
+    enum class FlingMode {
+        NO_FLING,
+
+        /**
+         * This is used for the closing target in custom cross-activity back animations. When the
+         * back gesture is flung, the closing target shrinks a bit further with a spring motion.
+         */
+        FLING_SHRINK,
+
+        /**
+         * This is used for the closing and opening target in the default cross-activity back
+         * animation. When the back gesture is flung, the closing and opening targets shrink a
+         * bit further and then bounce back with a spring motion.
+         */
+        FLING_BOUNCE
     }
 }
 
 // The target will loose focus when alpha == 0, so keep a minimum value for it.
-private fun keepMinimumAlpha(transAlpha: Float): Float {
-    return max(transAlpha.toDouble(), 0.005).toFloat()
-}
+private fun keepMinimumAlpha(transAlpha: Float) = max(transAlpha, 0.005f)
 
 private fun isDarkMode(context: Context): Boolean {
     return context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
index c4aafea..c738ce5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
@@ -57,7 +57,6 @@
     private var enterAnimation: Animation? = null
     private var closeAnimation: Animation? = null
     private val transformation = Transformation()
-    private var gestureProgress = 0f
 
     override val allowEnteringYShift = false
 
@@ -105,7 +104,6 @@
     }
 
     override fun getPreCommitEnteringBaseTransformation(progress: Float): Transformation {
-        gestureProgress = progress
         transformation.clear()
         enterAnimation!!.getTransformationAt(progress * PRE_COMMIT_MAX_PROGRESS, transformation)
         return transformation
@@ -134,7 +132,13 @@
         if (closingTarget == null || enteringTarget == null) return
 
         val closingProgress = closeAnimation!!.getPostCommitProgress(linearProgress)
-        applyTransform(closingTarget!!.leash, currentClosingRect, closingProgress, closeAnimation!!)
+        applyTransform(
+            closingTarget!!.leash,
+            currentClosingRect,
+            closingProgress,
+            closeAnimation!!,
+            FlingMode.FLING_SHRINK
+        )
         val enteringProgress = MathUtils.lerp(
             gestureProgress * PRE_COMMIT_MAX_PROGRESS,
             1f,
@@ -144,7 +148,8 @@
             enteringTarget!!.leash,
             currentEnteringRect,
             enteringProgress,
-            enterAnimation!!
+            enterAnimation!!,
+            FlingMode.NO_FLING
         )
         applyTransaction()
     }
@@ -153,11 +158,12 @@
         leash: SurfaceControl,
         rect: RectF,
         progress: Float,
-        animation: Animation
+        animation: Animation,
+        flingMode: FlingMode
     ) {
         transformation.clear()
         animation.getTransformationAt(progress, transformation)
-        applyTransform(leash, rect, transformation.alpha, transformation)
+        applyTransform(leash, rect, transformation.alpha, transformation, flingMode)
     }
 
     override fun finishAnimation() {
@@ -166,7 +172,6 @@
         enterAnimation?.reset()
         enterAnimation = null
         transformation.clear()
-        gestureProgress = 0f
         super.finishAnimation()
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
index 44752fe..3b5eb36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
@@ -43,7 +43,7 @@
         Choreographer.getInstance()
     ) {
 
-    private val postCommitInterpolator = Interpolators.FAST_OUT_SLOW_IN
+    private val postCommitInterpolator = Interpolators.EMPHASIZED
     private val enteringStartOffset =
         context.resources.getDimension(R.dimen.cross_activity_back_entering_start_offset)
     override val allowEnteringYShift = true
@@ -87,17 +87,27 @@
 
     override fun onPostCommitProgress(linearProgress: Float) {
         super.onPostCommitProgress(linearProgress)
-        val closingAlpha = max(1f - linearProgress * 2, 0f)
+        val closingAlpha = max(1f - linearProgress * 5, 0f)
         val progress = postCommitInterpolator.getInterpolation(linearProgress)
         currentClosingRect.setInterpolatedRectF(startClosingRect, targetClosingRect, progress)
-        applyTransform(closingTarget?.leash, currentClosingRect, closingAlpha)
+        applyTransform(
+            closingTarget?.leash,
+            currentClosingRect,
+            closingAlpha,
+            flingMode = FlingMode.FLING_BOUNCE
+        )
         currentEnteringRect.setInterpolatedRectF(startEnteringRect, targetEnteringRect, progress)
-        applyTransform(enteringTarget?.leash, currentEnteringRect, 1f)
+        applyTransform(
+            enteringTarget?.leash,
+            currentEnteringRect,
+            1f,
+            flingMode = FlingMode.FLING_BOUNCE
+        )
         applyTransaction()
     }
 
 
     companion object {
-        private const val POST_COMMIT_DURATION = 300L
+        private const val POST_COMMIT_DURATION = 450L
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index c79eef7e..cd478e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -143,6 +143,10 @@
     }
 
     public static boolean handles(TransitionInfo info) {
+        // There is no animation for screen-wake unless we are immediately unlocking.
+        if (info.getType() == WindowManager.TRANSIT_WAKE && !info.isKeyguardGoingAway()) {
+            return false;
+        }
         return (info.getFlags() & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0;
     }
 
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt
index 71072a5..d91c7e6 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt
@@ -16,9 +16,9 @@
 
 package com.android.settingslib.spa.widget.ui
 
-import android.content.ClipData
-import android.content.ClipboardManager
 import android.content.Context
+import androidx.compose.ui.platform.ClipboardManager
+import androidx.compose.ui.platform.LocalClipboardManager
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.longClick
@@ -63,9 +63,9 @@
 
     @Test
     fun onCopy_saveToClipboard() {
-        val clipboardManager = context.getSystemService(ClipboardManager::class.java)!!
-        clipboardManager.setPrimaryClip(ClipData.newPlainText("", ""))
+        var clipboardManager: ClipboardManager? = null
         composeTestRule.setContent {
+            clipboardManager = LocalClipboardManager.current
             CopyableBody(TEXT)
         }
 
@@ -74,7 +74,7 @@
         }
         composeTestRule.onNodeWithText(context.getString(android.R.string.copy)).performClick()
 
-        assertThat(clipboardManager.primaryClip!!.getItemAt(0).text.toString()).isEqualTo(TEXT)
+        assertThat(clipboardManager?.getText()?.text).isEqualTo(TEXT)
     }
 
     private companion object {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index a90f82e..c329384 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -1,9 +1,16 @@
 package com.android.systemui.communal.ui.compose
 
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.rememberInfiniteTransition
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.background
+import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
@@ -13,12 +20,20 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.CommunalSwipeDetector
@@ -36,8 +51,10 @@
 import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.observableTransitionState
 import com.android.compose.animation.scene.transitions
+import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.systemui.Flags
 import com.android.systemui.Flags.glanceableHubFullscreenSwipe
+import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
 import com.android.systemui.communal.ui.compose.extensions.allowGestures
@@ -102,6 +119,10 @@
     val touchesAllowed by viewModel.touchesAllowed.collectAsStateWithLifecycle(initialValue = false)
     val showGestureIndicator by
         viewModel.showGestureIndicator.collectAsStateWithLifecycle(initialValue = false)
+    val backgroundType by
+        viewModel.communalBackground.collectAsStateWithLifecycle(
+            initialValue = CommunalBackgroundType.DEFAULT
+        )
     val state: MutableSceneTransitionLayoutState = remember {
         MutableSceneTransitionLayoutState(
             initialScene = currentSceneKey,
@@ -174,7 +195,7 @@
             userActions =
                 mapOf(Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank)
         ) {
-            CommunalScene(colors, content)
+            CommunalScene(backgroundType, colors, content)
         }
     }
 
@@ -186,17 +207,87 @@
 /** Scene containing the glanceable hub UI. */
 @Composable
 private fun SceneScope.CommunalScene(
+    backgroundType: CommunalBackgroundType,
     colors: CommunalColors,
     content: CommunalContent,
     modifier: Modifier = Modifier,
 ) {
-    val backgroundColor by colors.backgroundColor.collectAsStateWithLifecycle()
-
-    Box(
-        modifier =
-            Modifier.element(Communal.Elements.Scrim)
-                .fillMaxSize()
-                .background(Color(backgroundColor.toArgb())),
-    )
+    Box(modifier = Modifier.element(Communal.Elements.Scrim).fillMaxSize()) {
+        when (backgroundType) {
+            CommunalBackgroundType.DEFAULT -> DefaultBackground(colors = colors)
+            CommunalBackgroundType.STATIC_GRADIENT -> StaticLinearGradient()
+            CommunalBackgroundType.ANIMATED -> AnimatedLinearGradient()
+        }
+    }
     with(content) { Content(modifier = modifier) }
 }
+
+/** Default background of the hub, a single color */
+@Composable
+private fun BoxScope.DefaultBackground(
+    colors: CommunalColors,
+) {
+    val backgroundColor by colors.backgroundColor.collectAsStateWithLifecycle()
+    Box(
+        modifier = Modifier.matchParentSize().background(Color(backgroundColor.toArgb())),
+    )
+}
+
+/** Experimental hub background, static linear gradient */
+@Composable
+private fun BoxScope.StaticLinearGradient() {
+    val colors = LocalAndroidColorScheme.current
+    Box(
+        Modifier.matchParentSize()
+            .background(
+                Brush.linearGradient(colors = listOf(colors.primary, colors.primaryContainer)),
+            )
+    )
+    BackgroundTopScrim()
+}
+
+/** Experimental hub background, animated linear gradient */
+@Composable
+private fun BoxScope.AnimatedLinearGradient() {
+    val colors = LocalAndroidColorScheme.current
+    Box(
+        Modifier.matchParentSize()
+            .animatedGradientBackground(colors = listOf(colors.primary, colors.primaryContainer))
+    )
+    BackgroundTopScrim()
+}
+
+/** Scrim placed on top of the background in order to dim/bright colors */
+@Composable
+private fun BoxScope.BackgroundTopScrim() {
+    val darkTheme = isSystemInDarkTheme()
+    val scrimOnTopColor = if (darkTheme) Color.Black else Color.White
+    Box(Modifier.matchParentSize().alpha(0.34f).background(scrimOnTopColor))
+}
+
+/** Modifier which sets the background of a composable to an animated gradient */
+@Composable
+private fun Modifier.animatedGradientBackground(colors: List<Color>): Modifier = composed {
+    var size by remember { mutableStateOf(IntSize.Zero) }
+    val transition = rememberInfiniteTransition(label = "scrim background")
+    val startOffsetX by
+        transition.animateFloat(
+            initialValue = -size.width.toFloat(),
+            targetValue = size.width.toFloat(),
+            animationSpec =
+                infiniteRepeatable(
+                    animation = tween(durationMillis = 5_000, easing = LinearEasing),
+                    repeatMode = RepeatMode.Reverse,
+                ),
+            label = "scrim start offset"
+        )
+    background(
+            brush =
+                Brush.linearGradient(
+                    colors = colors,
+                    start = Offset(startOffsetX, 0f),
+                    end = Offset(startOffsetX + size.width.toFloat(), size.height.toFloat()),
+                )
+        )
+        .onGloballyPositioned { size = it.size }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
index da29d58..48af8cd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
@@ -16,16 +16,13 @@
 
 package com.android.systemui.volume.panel.component.spatialaudio
 
-import com.android.systemui.volume.panel.component.button.ui.composable.ButtonComponent
 import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
 import com.android.systemui.volume.panel.component.spatial.domain.SpatialAudioAvailabilityCriteria
-import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel
-import com.android.systemui.volume.panel.component.spatialaudio.ui.composable.SpatialAudioPopup
+import com.android.systemui.volume.panel.component.spatialaudio.ui.composable.SpatialAudioComponent
 import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
 import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
 import dagger.Binds
 import dagger.Module
-import dagger.Provides
 import dagger.multibindings.IntoMap
 import dagger.multibindings.StringKey
 
@@ -40,14 +37,8 @@
         criteria: SpatialAudioAvailabilityCriteria
     ): ComponentAvailabilityCriteria
 
-    companion object {
-
-        @Provides
-        @IntoMap
-        @StringKey(VolumePanelComponents.SPATIAL_AUDIO)
-        fun provideVolumePanelUiComponent(
-            viewModel: SpatialAudioViewModel,
-            popup: SpatialAudioPopup,
-        ): VolumePanelUiComponent = ButtonComponent(viewModel.spatialAudioButton, popup::show)
-    }
+    @Binds
+    @IntoMap
+    @StringKey(VolumePanelComponents.SPATIAL_AUDIO)
+    fun bindVolumePanelUiComponent(component: SpatialAudioComponent): VolumePanelUiComponent
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioComponent.kt
new file mode 100644
index 0000000..2d89b5c
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioComponent.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatialaudio.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.volume.panel.component.button.ui.composable.ButtonComponent
+import com.android.systemui.volume.panel.component.button.ui.composable.ToggleButtonComponent
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
+import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel
+import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
+import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
+import javax.inject.Inject
+
+/** [ComposeVolumePanelUiComponent] that represents spatial audio button in the Volume Panel. */
+class SpatialAudioComponent
+@Inject
+constructor(
+    private val viewModel: SpatialAudioViewModel,
+    private val popup: SpatialAudioPopup,
+) : ComposeVolumePanelUiComponent {
+
+    @Composable
+    override fun VolumePanelComposeScope.Content(modifier: Modifier) {
+        val shouldUsePopup by viewModel.shouldUsePopup.collectAsStateWithLifecycle()
+
+        val buttonComponent: ComposeVolumePanelUiComponent =
+            remember(shouldUsePopup) {
+                if (shouldUsePopup) {
+                    ButtonComponent(viewModel.spatialAudioButton, popup::show)
+                } else {
+                    ToggleButtonComponent(viewModel.spatialAudioButton) {
+                        if (it) {
+                            viewModel.setEnabled(SpatialAudioEnabledModel.SpatialAudioEnabled)
+                        } else {
+                            viewModel.setEnabled(SpatialAudioEnabledModel.Disabled)
+                        }
+                    }
+                }
+            }
+        with(buttonComponent) { Content(modifier) }
+    }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 5d1a7c5..7fd3a176 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -27,11 +27,12 @@
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.runtime.snapshots.SnapshotStateMap
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.lerp
+import androidx.compose.ui.graphics.colorspace.ColorSpaces
 import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.lerp
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastCoerceIn
-import androidx.compose.ui.util.lerp
+import androidx.compose.ui.util.fastLastOrNull
+import kotlin.math.roundToInt
 
 /**
  * A [State] whose [value] is animated.
@@ -74,7 +75,7 @@
     key: ValueKey,
     canOverflow: Boolean = true,
 ): AnimatedState<Int> {
-    return animateSceneValueAsState(value, key, ::lerp, canOverflow)
+    return animateSceneValueAsState(value, key, SharedIntType, canOverflow)
 }
 
 /**
@@ -88,7 +89,19 @@
     key: ValueKey,
     canOverflow: Boolean = true,
 ): AnimatedState<Int> {
-    return animateElementValueAsState(value, key, ::lerp, canOverflow)
+    return animateElementValueAsState(value, key, SharedIntType, canOverflow)
+}
+
+private object SharedIntType : SharedValueType<Int, Int> {
+    override val unspecifiedValue: Int = Int.MIN_VALUE
+    override val zeroDeltaValue: Int = 0
+
+    override fun lerp(a: Int, b: Int, progress: Float): Int =
+        androidx.compose.ui.util.lerp(a, b, progress)
+
+    override fun diff(a: Int, b: Int): Int = a - b
+
+    override fun addWeighted(a: Int, b: Int, bWeight: Float): Int = (a + b * bWeight).roundToInt()
 }
 
 /**
@@ -102,7 +115,7 @@
     key: ValueKey,
     canOverflow: Boolean = true,
 ): AnimatedState<Float> {
-    return animateSceneValueAsState(value, key, ::lerp, canOverflow)
+    return animateSceneValueAsState(value, key, SharedFloatType, canOverflow)
 }
 
 /**
@@ -116,7 +129,19 @@
     key: ValueKey,
     canOverflow: Boolean = true,
 ): AnimatedState<Float> {
-    return animateElementValueAsState(value, key, ::lerp, canOverflow)
+    return animateElementValueAsState(value, key, SharedFloatType, canOverflow)
+}
+
+private object SharedFloatType : SharedValueType<Float, Float> {
+    override val unspecifiedValue: Float = Float.MIN_VALUE
+    override val zeroDeltaValue: Float = 0f
+
+    override fun lerp(a: Float, b: Float, progress: Float): Float =
+        androidx.compose.ui.util.lerp(a, b, progress)
+
+    override fun diff(a: Float, b: Float): Float = a - b
+
+    override fun addWeighted(a: Float, b: Float, bWeight: Float): Float = a + b * bWeight
 }
 
 /**
@@ -130,7 +155,7 @@
     key: ValueKey,
     canOverflow: Boolean = true,
 ): AnimatedState<Dp> {
-    return animateSceneValueAsState(value, key, ::lerp, canOverflow)
+    return animateSceneValueAsState(value, key, SharedDpType, canOverflow)
 }
 
 /**
@@ -144,7 +169,20 @@
     key: ValueKey,
     canOverflow: Boolean = true,
 ): AnimatedState<Dp> {
-    return animateElementValueAsState(value, key, ::lerp, canOverflow)
+    return animateElementValueAsState(value, key, SharedDpType, canOverflow)
+}
+
+private object SharedDpType : SharedValueType<Dp, Dp> {
+    override val unspecifiedValue: Dp = Dp.Unspecified
+    override val zeroDeltaValue: Dp = 0.dp
+
+    override fun lerp(a: Dp, b: Dp, progress: Float): Dp {
+        return androidx.compose.ui.unit.lerp(a, b, progress)
+    }
+
+    override fun diff(a: Dp, b: Dp): Dp = a - b
+
+    override fun addWeighted(a: Dp, b: Dp, bWeight: Float): Dp = a + b * bWeight
 }
 
 /**
@@ -157,7 +195,7 @@
     value: Color,
     key: ValueKey,
 ): AnimatedState<Color> {
-    return animateSceneValueAsState(value, key, ::lerp, canOverflow = false)
+    return animateSceneValueAsState(value, key, SharedColorType, canOverflow = false)
 }
 
 /**
@@ -170,9 +208,56 @@
     value: Color,
     key: ValueKey,
 ): AnimatedState<Color> {
-    return animateElementValueAsState(value, key, ::lerp, canOverflow = false)
+    return animateElementValueAsState(value, key, SharedColorType, canOverflow = false)
 }
 
+private object SharedColorType : SharedValueType<Color, ColorDelta> {
+    override val unspecifiedValue: Color = Color.Unspecified
+    override val zeroDeltaValue: ColorDelta = ColorDelta(0f, 0f, 0f, 0f)
+
+    override fun lerp(a: Color, b: Color, progress: Float): Color {
+        return androidx.compose.ui.graphics.lerp(a, b, progress)
+    }
+
+    override fun diff(a: Color, b: Color): ColorDelta {
+        // Similar to lerp, we convert colors to the Oklab color space to perform operations on
+        // colors.
+        val aOklab = a.convert(ColorSpaces.Oklab)
+        val bOklab = b.convert(ColorSpaces.Oklab)
+        return ColorDelta(
+            red = aOklab.red - bOklab.red,
+            green = aOklab.green - bOklab.green,
+            blue = aOklab.blue - bOklab.blue,
+            alpha = aOklab.alpha - bOklab.alpha,
+        )
+    }
+
+    override fun addWeighted(a: Color, b: ColorDelta, bWeight: Float): Color {
+        val aOklab = a.convert(ColorSpaces.Oklab)
+        return Color(
+                red = aOklab.red + b.red * bWeight,
+                green = aOklab.green + b.green * bWeight,
+                blue = aOklab.blue + b.blue * bWeight,
+                alpha = aOklab.alpha + b.alpha * bWeight,
+                colorSpace = ColorSpaces.Oklab,
+            )
+            .convert(aOklab.colorSpace)
+    }
+}
+
+/**
+ * Represents the diff between two colors in the same color space.
+ *
+ * Note: This class is necessary because Color() checks the bounds of its values and UncheckedColor
+ * is internal.
+ */
+private class ColorDelta(
+    val red: Float,
+    val green: Float,
+    val blue: Float,
+    val alpha: Float,
+)
+
 @Composable
 internal fun <T> animateSharedValueAsState(
     layoutImpl: SceneTransitionLayoutImpl,
@@ -180,23 +265,22 @@
     element: ElementKey?,
     key: ValueKey,
     value: T,
-    lerp: (T, T, Float) -> T,
+    type: SharedValueType<T, *>,
     canOverflow: Boolean,
 ): AnimatedState<T> {
     DisposableEffect(layoutImpl, scene, element, key) {
         // Create the associated maps that hold the current value for each (element, scene) pair.
         val valueMap = layoutImpl.sharedValues.getOrPut(key) { mutableMapOf() }
-        val sceneToValueMap =
-            valueMap.getOrPut(element) { SnapshotStateMap<SceneKey, Any>() }
-                as SnapshotStateMap<SceneKey, T>
-        sceneToValueMap[scene] = value
+        val sharedValue = valueMap.getOrPut(element) { SharedValue(type) } as SharedValue<T, *>
+        val targetValues = sharedValue.targetValues
+        targetValues[scene] = value
 
         onDispose {
             // Remove the value associated to the current scene, and eventually remove the maps if
             // they are empty.
-            sceneToValueMap.remove(scene)
+            targetValues.remove(scene)
 
-            if (sceneToValueMap.isEmpty() && valueMap[element] === sceneToValueMap) {
+            if (targetValues.isEmpty() && valueMap[element] === sharedValue) {
                 valueMap.remove(element)
 
                 if (valueMap.isEmpty() && layoutImpl.sharedValues[key] === valueMap) {
@@ -208,34 +292,25 @@
 
     // Update the current value. Note that side effects run after disposable effects, so we know
     // that the associated maps were created at this point.
-    SideEffect { sceneToValueMap<T>(layoutImpl, key, element)[scene] = value }
-
-    return remember(layoutImpl, scene, element, lerp, canOverflow) {
-        object : AnimatedState<T> {
-            override val value: T
-                get() = value(layoutImpl, scene, element, key, lerp, canOverflow)
-
-            @Composable
-            override fun unsafeCompositionState(initialValue: T): State<T> {
-                val state = remember { mutableStateOf(initialValue) }
-
-                val animatedState = this
-                LaunchedEffect(animatedState) {
-                    snapshotFlow { animatedState.value }.collect { state.value = it }
-                }
-
-                return state
-            }
+    SideEffect {
+        if (value == type.unspecifiedValue) {
+            error("value is equal to $value, which is the undefined value for this type.")
         }
+
+        sharedValue<T, Any>(layoutImpl, key, element).targetValues[scene] = value
+    }
+
+    return remember(layoutImpl, scene, element, canOverflow) {
+        AnimatedStateImpl<T, Any>(layoutImpl, scene, element, key, canOverflow)
     }
 }
 
-private fun <T> sceneToValueMap(
+private fun <T, Delta> sharedValue(
     layoutImpl: SceneTransitionLayoutImpl,
     key: ValueKey,
     element: ElementKey?
-): MutableMap<SceneKey, T> {
-    return layoutImpl.sharedValues[key]?.get(element)?.let { it as SnapshotStateMap<SceneKey, T> }
+): SharedValue<T, Delta> {
+    return layoutImpl.sharedValues[key]?.get(element)?.let { it as SharedValue<T, Delta> }
         ?: error(valueReadTooEarlyMessage(key))
 }
 
@@ -244,62 +319,155 @@
         "means that you are reading it during composition, which you should not do. See the " +
         "documentation of AnimatedState for more information."
 
-private fun <T> value(
-    layoutImpl: SceneTransitionLayoutImpl,
-    scene: SceneKey,
-    element: ElementKey?,
-    key: ValueKey,
-    lerp: (T, T, Float) -> T,
-    canOverflow: Boolean,
-): T {
-    return valueOrNull(layoutImpl, scene, element, key, lerp, canOverflow)
-        ?: error(valueReadTooEarlyMessage(key))
+internal class SharedValue<T, Delta>(
+    val type: SharedValueType<T, Delta>,
+) {
+    /** The target value of this shared value for each scene. */
+    val targetValues = SnapshotStateMap<SceneKey, T>()
+
+    /** The last value of this shared value. */
+    var lastValue: T = type.unspecifiedValue
+
+    /** The value of this shared value before the last interruption (if any). */
+    var valueBeforeInterruption: T = type.unspecifiedValue
+
+    /** The delta value to add to this shared value to have smoother interruptions. */
+    var valueInterruptionDelta = type.zeroDeltaValue
+
+    /** The last transition that was used when the value of this shared state. */
+    var lastTransition: TransitionState.Transition? = null
 }
 
-private fun <T> valueOrNull(
-    layoutImpl: SceneTransitionLayoutImpl,
-    scene: SceneKey,
-    element: ElementKey?,
-    key: ValueKey,
-    lerp: (T, T, Float) -> T,
-    canOverflow: Boolean,
-): T? {
-    val sceneToValueMap = sceneToValueMap<T>(layoutImpl, key, element)
-    fun sceneValue(scene: SceneKey): T? = sceneToValueMap[scene]
+private class AnimatedStateImpl<T, Delta>(
+    private val layoutImpl: SceneTransitionLayoutImpl,
+    private val scene: SceneKey,
+    private val element: ElementKey?,
+    private val key: ValueKey,
+    private val canOverflow: Boolean,
+) : AnimatedState<T> {
+    override val value: T
+        get() = value()
 
-    return when (val transition = layoutImpl.state.transitionState) {
-        is TransitionState.Idle -> sceneValue(transition.currentScene)
-        is TransitionState.Transition -> {
-            // Note: no need to check for transition ready here given that all target values are
-            // defined during composition, we should already have the correct values to interpolate
-            // between here.
-            val fromValue = sceneValue(transition.fromScene)
-            val toValue = sceneValue(transition.toScene)
-            if (fromValue != null && toValue != null) {
-                if (fromValue == toValue) {
-                    // Optimization: avoid reading progress if the values are the same, so we don't
-                    // relayout/redraw for nothing.
-                    fromValue
-                } else {
-                    // In the case of bouncing, if the value remains constant during the overscroll,
-                    // we should use the value of the scene we are bouncing around.
-                    if (!canOverflow && transition is TransitionState.HasOverscrollProperties) {
-                        val bouncingScene = transition.bouncingScene
-                        if (bouncingScene != null) {
-                            return sceneValue(bouncingScene)
-                        }
+    private fun value(): T {
+        val sharedValue = sharedValue<T, Delta>(layoutImpl, key, element)
+        val transition = transition(sharedValue)
+        val value: T =
+            valueOrNull(sharedValue, transition)
+                // TODO(b/311600838): Remove this. We should not have to fallback to the current
+                // scene value, but we have to because code of removed nodes can still run if they
+                // are placed with a graphics layer.
+                ?: sharedValue[scene]
+                ?: error(valueReadTooEarlyMessage(key))
+        val interruptedValue = computeInterruptedValue(sharedValue, transition, value)
+        sharedValue.lastValue = interruptedValue
+        return interruptedValue
+    }
+
+    private operator fun SharedValue<T, *>.get(scene: SceneKey): T? = targetValues[scene]
+
+    private fun valueOrNull(
+        sharedValue: SharedValue<T, *>,
+        transition: TransitionState.Transition?,
+    ): T? {
+        if (transition == null) {
+            return sharedValue[layoutImpl.state.transitionState.currentScene]
+        }
+
+        val fromValue = sharedValue[transition.fromScene]
+        val toValue = sharedValue[transition.toScene]
+        return if (fromValue != null && toValue != null) {
+            if (fromValue == toValue) {
+                // Optimization: avoid reading progress if the values are the same, so we don't
+                // relayout/redraw for nothing.
+                fromValue
+            } else {
+                // In the case of bouncing, if the value remains constant during the overscroll, we
+                // should use the value of the scene we are bouncing around.
+                if (!canOverflow && transition is TransitionState.HasOverscrollProperties) {
+                    val bouncingScene = transition.bouncingScene
+                    if (bouncingScene != null) {
+                        return sharedValue[bouncingScene]
                     }
-
-                    val progress =
-                        if (canOverflow) transition.progress
-                        else transition.progress.fastCoerceIn(0f, 1f)
-                    lerp(fromValue, toValue, progress)
                 }
-            } else fromValue ?: toValue
+
+                val progress =
+                    if (canOverflow) transition.progress
+                    else transition.progress.fastCoerceIn(0f, 1f)
+                sharedValue.type.lerp(fromValue, toValue, progress)
+            }
+        } else fromValue ?: toValue
+    }
+
+    private fun transition(sharedValue: SharedValue<T, Delta>): TransitionState.Transition? {
+        val targetValues = sharedValue.targetValues
+        val transition =
+            if (element != null) {
+                layoutImpl.elements[element]?.sceneStates?.let { sceneStates ->
+                    layoutImpl.state.currentTransitions.fastLastOrNull { transition ->
+                        transition.fromScene in sceneStates || transition.toScene in sceneStates
+                    }
+                }
+            } else {
+                layoutImpl.state.currentTransitions.fastLastOrNull { transition ->
+                    transition.fromScene in targetValues || transition.toScene in targetValues
+                }
+            }
+
+        val previousTransition = sharedValue.lastTransition
+        sharedValue.lastTransition = transition
+
+        if (transition != previousTransition && transition != null && previousTransition != null) {
+            // The previous transition was interrupted by another transition.
+            sharedValue.valueBeforeInterruption = sharedValue.lastValue
+            sharedValue.valueInterruptionDelta = sharedValue.type.zeroDeltaValue
+        } else if (transition == null && previousTransition != null) {
+            // The transition was just finished.
+            sharedValue.valueBeforeInterruption = sharedValue.type.unspecifiedValue
+            sharedValue.valueInterruptionDelta = sharedValue.type.zeroDeltaValue
+        }
+
+        return transition
+    }
+
+    /**
+     * Compute what [value] should be if we take the
+     * [interruption progress][TransitionState.Transition.interruptionProgress] of [transition] into
+     * account.
+     */
+    private fun computeInterruptedValue(
+        sharedValue: SharedValue<T, Delta>,
+        transition: TransitionState.Transition?,
+        value: T,
+    ): T {
+        val type = sharedValue.type
+        if (sharedValue.valueBeforeInterruption != type.unspecifiedValue) {
+            sharedValue.valueInterruptionDelta =
+                type.diff(sharedValue.valueBeforeInterruption, value)
+            sharedValue.valueBeforeInterruption = type.unspecifiedValue
+        }
+
+        val delta = sharedValue.valueInterruptionDelta
+        return if (delta == type.zeroDeltaValue || transition == null) {
+            value
+        } else {
+            val interruptionProgress = transition.interruptionProgress(layoutImpl)
+            if (interruptionProgress == 0f) {
+                value
+            } else {
+                type.addWeighted(value, delta, interruptionProgress)
+            }
         }
     }
-    // TODO(b/311600838): Remove this. We should not have to fallback to the current scene value,
-    // but we have to because code of removed nodes can still run if they are placed with a graphics
-    // layer.
-    ?: sceneValue(scene)
+
+    @Composable
+    override fun unsafeCompositionState(initialValue: T): State<T> {
+        val state = remember { mutableStateOf(initialValue) }
+
+        val animatedState = this
+        LaunchedEffect(animatedState) {
+            snapshotFlow { animatedState.value }.collect { state.value = it }
+        }
+
+        return state
+    }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 1f81245..e9633c2 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -148,9 +148,9 @@
         val swipes = computeSwipes(fromScene, startedPosition, pointersDown)
         val result =
             swipes.findUserActionResult(fromScene, overSlop, true)
-            // As we were unable to locate a valid target scene, the initial SwipeTransition
-            // cannot be defined. Consequently, a simple NoOp Controller will be returned.
-            ?: return NoOpDragController
+                // As we were unable to locate a valid target scene, the initial SwipeTransition
+                // cannot be defined. Consequently, a simple NoOp Controller will be returned.
+                ?: return NoOpDragController
 
         return updateDragController(
             swipes = swipes,
@@ -521,6 +521,7 @@
         }
 
     return SwipeTransition(
+        layoutImpl = layoutImpl,
         layoutState = layoutState,
         coroutineScope = coroutineScope,
         key = result.transitionKey,
@@ -534,6 +535,7 @@
 
 private fun SwipeTransition(old: SwipeTransition): SwipeTransition {
     return SwipeTransition(
+            layoutImpl = old.layoutImpl,
             layoutState = old.layoutState,
             coroutineScope = old.coroutineScope,
             key = old.key,
@@ -550,6 +552,7 @@
 }
 
 private class SwipeTransition(
+    val layoutImpl: SceneTransitionLayoutImpl,
     val layoutState: BaseSceneTransitionLayoutState,
     val coroutineScope: CoroutineScope,
     val key: TransitionKey?,
@@ -607,6 +610,12 @@
 
     override val overscrollScope: OverscrollScope =
         object : OverscrollScope {
+            override val density: Float
+                get() = layoutImpl.density.density
+
+            override val fontScale: Float
+                get() = layoutImpl.density.fontScale
+
             override val absoluteDistance: Float
                 get() = distance().absoluteValue
         }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index 4b20aca..be005ea 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -77,7 +77,7 @@
     override fun <T> animateElementValueAsState(
         value: T,
         key: ValueKey,
-        lerp: (start: T, stop: T, fraction: Float) -> T,
+        type: SharedValueType<T, *>,
         canOverflow: Boolean
     ): AnimatedState<T> {
         return animateSharedValueAsState(
@@ -86,7 +86,7 @@
             element,
             key,
             value,
-            lerp,
+            type,
             canOverflow,
         )
     }
@@ -184,8 +184,7 @@
                 fromSceneZIndex = layoutImpl.scenes.getValue(transition.fromScene).zIndex,
                 toSceneZIndex = layoutImpl.scenes.getValue(transition.toScene).zIndex,
             ) != null
-        }
-            ?: return false
+        } ?: return false
 
     // Always compose movable elements in the scene picked by their scene picker.
     return shouldDrawOrComposeSharedElement(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 6fef33c..936f4ba 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -24,7 +24,6 @@
 import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.approachLayout
 import androidx.compose.ui.platform.testTag
@@ -69,7 +68,6 @@
     }
 
     @Composable
-    @OptIn(ExperimentalComposeUiApi::class)
     fun Content(modifier: Modifier = Modifier) {
         Box(
             modifier
@@ -96,6 +94,7 @@
     private val layoutImpl: SceneTransitionLayoutImpl,
     private val scene: Scene,
 ) : SceneScope, ElementStateScope by layoutImpl.elementStateScope {
+    override val sceneKey: SceneKey = scene.key
     override val layoutState: SceneTransitionLayoutState = layoutImpl.state
 
     override fun Modifier.element(key: ElementKey): Modifier {
@@ -124,7 +123,7 @@
     override fun <T> animateSceneValueAsState(
         value: T,
         key: ValueKey,
-        lerp: (T, T, Float) -> T,
+        type: SharedValueType<T, *>,
         canOverflow: Boolean
     ): AnimatedState<T> {
         return animateSharedValueAsState(
@@ -133,7 +132,7 @@
             element = null,
             key = key,
             value = value,
-            lerp = lerp,
+            type = type,
             canOverflow = canOverflow,
         )
     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index cf8c584..2946b04 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -167,6 +167,9 @@
 @Stable
 @ElementDsl
 interface BaseSceneScope : ElementStateScope {
+    /** The key of this scene. */
+    val sceneKey: SceneKey
+
     /** The state of the [SceneTransitionLayout] in which this scene is contained. */
     val layoutState: SceneTransitionLayoutState
 
@@ -285,9 +288,7 @@
      *
      * @param value the value of this shared value in the current scene.
      * @param key the key of this shared value.
-     * @param lerp the *linear* interpolation function that should be used to interpolate between
-     *   two different values. Note that it has to be linear because the [fraction] passed to this
-     *   interpolator is already interpolated.
+     * @param type the [SharedValueType] of this animated value.
      * @param canOverflow whether this value can overflow past the values it is interpolated
      *   between, for instance because the transition is animated using a bouncy spring.
      * @see animateSceneIntAsState
@@ -299,11 +300,39 @@
     fun <T> animateSceneValueAsState(
         value: T,
         key: ValueKey,
-        lerp: (start: T, stop: T, fraction: Float) -> T,
+        type: SharedValueType<T, *>,
         canOverflow: Boolean,
     ): AnimatedState<T>
 }
 
+/**
+ * The type of a shared value animated using [ElementScope.animateElementValueAsState] or
+ * [SceneScope.animateSceneValueAsState].
+ */
+@Stable
+interface SharedValueType<T, Delta> {
+    /** The unspecified value for this type. */
+    val unspecifiedValue: T
+
+    /**
+     * The zero value of this type. It should be equal to what [diff(x, x)] returns for any value of
+     * x.
+     */
+    val zeroDeltaValue: Delta
+
+    /**
+     * Return the linear interpolation of [a] and [b] at the given [progress], i.e. `a + (b - a) *
+     * progress`.
+     */
+    fun lerp(a: T, b: T, progress: Float): T
+
+    /** Return `a - b`. */
+    fun diff(a: T, b: T): Delta
+
+    /** Return `a + b * bWeight`. */
+    fun addWeighted(a: T, b: Delta, bWeight: Float): T
+}
+
 @Stable
 @ElementDsl
 interface ElementScope<ContentScope> {
@@ -312,9 +341,7 @@
      *
      * @param value the value of this shared value in the current scene.
      * @param key the key of this shared value.
-     * @param lerp the *linear* interpolation function that should be used to interpolate between
-     *   two different values. Note that it has to be linear because the [fraction] passed to this
-     *   interpolator is already interpolated.
+     * @param type the [SharedValueType] of this animated value.
      * @param canOverflow whether this value can overflow past the values it is interpolated
      *   between, for instance because the transition is animated using a bouncy spring.
      * @see animateElementIntAsState
@@ -326,7 +353,7 @@
     fun <T> animateElementValueAsState(
         value: T,
         key: ValueKey,
-        lerp: (start: T, stop: T, fraction: Float) -> T,
+        type: SharedValueType<T, *>,
         canOverflow: Boolean,
     ): AnimatedState<T>
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index c614265..5fa7c87 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -85,15 +85,14 @@
      * The different values of a shared value keyed by a a [ValueKey] and the different elements and
      * scenes it is associated to.
      */
-    private var _sharedValues:
-        MutableMap<ValueKey, MutableMap<ElementKey?, SnapshotStateMap<SceneKey, *>>>? =
+    private var _sharedValues: MutableMap<ValueKey, MutableMap<ElementKey?, SharedValue<*, *>>>? =
         null
-    internal val sharedValues:
-        MutableMap<ValueKey, MutableMap<ElementKey?, SnapshotStateMap<SceneKey, *>>>
+    internal val sharedValues: MutableMap<ValueKey, MutableMap<ElementKey?, SharedValue<*, *>>>
         get() =
             _sharedValues
-                ?: mutableMapOf<ValueKey, MutableMap<ElementKey?, SnapshotStateMap<SceneKey, *>>>()
-                    .also { _sharedValues = it }
+                ?: mutableMapOf<ValueKey, MutableMap<ElementKey?, SharedValue<*, *>>>().also {
+                    _sharedValues = it
+                }
 
     // TODO(b/317958526): Lazily allocate scene gesture handlers the first time they are needed.
     private val horizontalDraggableHandler: DraggableHandlerImpl
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index a5b6d24..44affd9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -457,7 +457,7 @@
      */
     internal fun startTransition(
         transition: TransitionState.Transition,
-        transitionKey: TransitionKey?,
+        transitionKey: TransitionKey? = null,
         chain: Boolean = true,
     ) {
         checkThread()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index a4682ff..465a410 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -20,6 +20,7 @@
 import androidx.compose.animation.core.SpringSpec
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
@@ -192,7 +193,7 @@
     )
 }
 
-interface OverscrollScope {
+interface OverscrollScope : Density {
     /**
      * Return the absolute distance between fromScene and toScene, if available, otherwise
      * [DistanceUnspecified].
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index e8854cf..6e8b208 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -32,7 +32,12 @@
 import androidx.compose.ui.unit.lerp
 import androidx.compose.ui.util.lerp
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.TestScenes.SceneD
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertThrows
 import org.junit.Rule
 import org.junit.Test
@@ -130,8 +135,8 @@
                 // The transition lasts 64ms = 4 frames.
                 spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
             },
-            fromScene = TestScenes.SceneA,
-            toScene = TestScenes.SceneB,
+            fromScene = SceneA,
+            toScene = SceneB,
         ) {
             before {
                 assertThat(lastValueInFrom).isEqualTo(fromValues)
@@ -189,8 +194,8 @@
                 // The transition lasts 64ms = 4 frames.
                 spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
             },
-            fromScene = TestScenes.SceneA,
-            toScene = TestScenes.SceneB,
+            fromScene = SceneA,
+            toScene = SceneB,
         ) {
             before {
                 assertThat(lastValueInFrom).isEqualTo(fromValues)
@@ -243,8 +248,8 @@
                 // The transition lasts 64ms = 4 frames.
                 spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
             },
-            fromScene = TestScenes.SceneA,
-            toScene = TestScenes.SceneB,
+            fromScene = SceneA,
+            toScene = SceneB,
         ) {
             before {
                 assertThat(lastValueInFrom).isEqualTo(fromValues)
@@ -381,4 +386,61 @@
             }
         }
     }
+
+    @Test
+    fun animatedValueIsUsingLastTransition() = runTest {
+        val state =
+            rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA, transitions {}) }
+
+        val foo = ValueKey("foo")
+        val bar = ValueKey("bar")
+        val lastValues = mutableMapOf<ValueKey, MutableMap<SceneKey, Float>>()
+
+        @Composable
+        fun SceneScope.animateFloat(value: Float, key: ValueKey) {
+            val animatedValue = animateSceneFloatAsState(value, key)
+            LaunchedEffect(animatedValue) {
+                snapshotFlow { animatedValue.value }
+                    .collect { lastValues.getOrPut(key) { mutableMapOf() }[sceneKey] = it }
+            }
+        }
+
+        rule.setContent {
+            SceneTransitionLayout(state) {
+                // foo goes from 0f to 100f in A => B.
+                scene(SceneA) { animateFloat(0f, foo) }
+                scene(SceneB) { animateFloat(100f, foo) }
+
+                // bar goes from 0f to 10f in C => D.
+                scene(SceneC) { animateFloat(0f, bar) }
+                scene(SceneD) { animateFloat(10f, bar) }
+            }
+        }
+
+        rule.runOnUiThread {
+            // A => B is at 30%.
+            state.startTransition(
+                transition(
+                    from = SceneA,
+                    to = SceneB,
+                    progress = { 0.3f },
+                    onFinish = neverFinish(),
+                )
+            )
+
+            // C => D is at 70%.
+            state.startTransition(transition(from = SceneC, to = SceneD, progress = { 0.7f }))
+        }
+        rule.waitForIdle()
+
+        assertThat(lastValues[foo]?.get(SceneA)).isWithin(0.001f).of(30f)
+        assertThat(lastValues[foo]?.get(SceneB)).isWithin(0.001f).of(30f)
+        assertThat(lastValues[foo]?.get(SceneC)).isNull()
+        assertThat(lastValues[foo]?.get(SceneD)).isNull()
+
+        assertThat(lastValues[bar]?.get(SceneA)).isNull()
+        assertThat(lastValues[bar]?.get(SceneB)).isNull()
+        assertThat(lastValues[bar]?.get(SceneC)).isWithin(0.001f).of(7f)
+        assertThat(lastValues[bar]?.get(SceneD)).isWithin(0.001f).of(7f)
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 9692fae..beb74bc 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -59,6 +59,7 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.lerp
+import androidx.compose.ui.util.lerp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.compose.animation.scene.TestScenes.SceneA
 import com.android.compose.animation.scene.TestScenes.SceneB
@@ -348,7 +349,7 @@
                     ),
                 onLayoutImpl = { nullableLayoutImpl = it },
             ) {
-                scene(SceneA) { /* Nothing */}
+                scene(SceneA) { /* Nothing */ }
                 scene(SceneB) { Box(Modifier.element(key)) }
                 scene(SceneC) {
                     when (sceneCState) {
@@ -1083,10 +1084,17 @@
             }
 
         val layoutSize = DpSize(200.dp, 100.dp)
+        val lastValues = mutableMapOf<SceneKey, Float>()
 
         @Composable
-        fun SceneScope.Foo(size: Dp, modifier: Modifier = Modifier) {
-            Box(modifier.element(TestElements.Foo).size(size))
+        fun SceneScope.Foo(size: Dp, value: Float, modifier: Modifier = Modifier) {
+            val sceneKey = this.sceneKey
+            Element(TestElements.Foo, modifier.size(size)) {
+                val animatedValue = animateElementFloatAsState(value, TestValues.Value1)
+                LaunchedEffect(animatedValue) {
+                    snapshotFlow { animatedValue.value }.collect { lastValues[sceneKey] = it }
+                }
+            }
         }
 
         // The size of Foo when idle in A, B or C.
@@ -1094,6 +1102,11 @@
         val sizeInB = 30.dp
         val sizeInC = 50.dp
 
+        // The target value when idle in A, B, or C.
+        val valueInA = 0f
+        val valueInB = 100f
+        val valueInC = 200f
+
         lateinit var layoutImpl: SceneTransitionLayoutImpl
         rule.setContent {
             SceneTransitionLayoutForTesting(
@@ -1103,7 +1116,9 @@
             ) {
                 // In scene A, Foo is aligned at the TopStart.
                 scene(SceneA) {
-                    Box(Modifier.fillMaxSize()) { Foo(sizeInA, Modifier.align(Alignment.TopStart)) }
+                    Box(Modifier.fillMaxSize()) {
+                        Foo(sizeInA, valueInA, Modifier.align(Alignment.TopStart))
+                    }
                 }
 
                 // In scene C, Foo is aligned at the BottomEnd, so it moves vertically when coming
@@ -1111,14 +1126,16 @@
                 // values and deltas are properly cleared once all transitions are done.
                 scene(SceneC) {
                     Box(Modifier.fillMaxSize()) {
-                        Foo(sizeInC, Modifier.align(Alignment.BottomEnd))
+                        Foo(sizeInC, valueInC, Modifier.align(Alignment.BottomEnd))
                     }
                 }
 
                 // In scene B, Foo is aligned at the TopEnd, so it moves horizontally when coming
                 // from A.
                 scene(SceneB) {
-                    Box(Modifier.fillMaxSize()) { Foo(sizeInB, Modifier.align(Alignment.TopEnd)) }
+                    Box(Modifier.fillMaxSize()) {
+                        Foo(sizeInB, valueInB, Modifier.align(Alignment.TopEnd))
+                    }
                 }
             }
         }
@@ -1134,6 +1151,10 @@
             .assertSizeIsEqualTo(sizeInA)
             .assertPositionInRootIsEqualTo(offsetInA.x, offsetInA.y)
 
+        assertThat(lastValues[SceneA]).isWithin(0.001f).of(valueInA)
+        assertThat(lastValues[SceneB]).isNull()
+        assertThat(lastValues[SceneC]).isNull()
+
         // Current transition is A => B at 50%.
         val aToBProgress = 0.5f
         val aToB =
@@ -1145,12 +1166,17 @@
             )
         val offsetInAToB = lerp(offsetInA, offsetInB, aToBProgress)
         val sizeInAToB = lerp(sizeInA, sizeInB, aToBProgress)
+        val valueInAToB = lerp(valueInA, valueInB, aToBProgress)
         rule.runOnUiThread { state.startTransition(aToB, transitionKey = null) }
         rule
             .onNode(isElement(TestElements.Foo, SceneB))
             .assertSizeIsEqualTo(sizeInAToB)
             .assertPositionInRootIsEqualTo(offsetInAToB.x, offsetInAToB.y)
 
+        assertThat(lastValues[SceneA]).isWithin(0.001f).of(valueInAToB)
+        assertThat(lastValues[SceneB]).isWithin(0.001f).of(valueInAToB)
+        assertThat(lastValues[SceneC]).isNull()
+
         // Start B => C at 0%.
         var bToCProgress by mutableFloatStateOf(0f)
         var interruptionProgress by mutableFloatStateOf(1f)
@@ -1167,6 +1193,11 @@
         // to the current transition offset and size.
         val offsetInterruptionDelta = offsetInAToB - offsetInB
         val sizeInterruptionDelta = sizeInAToB - sizeInB
+        val valueInterruptionDelta = valueInAToB - valueInB
+
+        assertThat(offsetInterruptionDelta).isNotEqualTo(DpOffset.Zero)
+        assertThat(sizeInterruptionDelta).isNotEqualTo(0.dp)
+        assertThat(valueInterruptionDelta).isNotEqualTo(0f)
 
         // Interruption progress is at 100% and bToC is at 0%, so Foo should be at the same offset
         // and size as right before the interruption.
@@ -1175,11 +1206,16 @@
             .assertPositionInRootIsEqualTo(offsetInAToB.x, offsetInAToB.y)
             .assertSizeIsEqualTo(sizeInAToB)
 
+        assertThat(lastValues[SceneA]).isWithin(0.001f).of(valueInAToB)
+        assertThat(lastValues[SceneB]).isWithin(0.001f).of(valueInAToB)
+        assertThat(lastValues[SceneC]).isWithin(0.001f).of(valueInAToB)
+
         // Move the transition forward at 30% and set the interruption progress to 50%.
         bToCProgress = 0.3f
         interruptionProgress = 0.5f
         val offsetInBToC = lerp(offsetInB, offsetInC, bToCProgress)
         val sizeInBToC = lerp(sizeInB, sizeInC, bToCProgress)
+        val valueInBToC = lerp(valueInB, valueInC, bToCProgress)
         val offsetInBToCWithInterruption =
             offsetInBToC +
                 DpOffset(
@@ -1187,6 +1223,9 @@
                     offsetInterruptionDelta.y * interruptionProgress,
                 )
         val sizeInBToCWithInterruption = sizeInBToC + sizeInterruptionDelta * interruptionProgress
+        val valueInBToCWithInterruption =
+            valueInBToC + valueInterruptionDelta * interruptionProgress
+
         rule.waitForIdle()
         rule
             .onNode(isElement(TestElements.Foo, SceneB))
@@ -1196,6 +1235,10 @@
             )
             .assertSizeIsEqualTo(sizeInBToCWithInterruption)
 
+        assertThat(lastValues[SceneA]).isWithin(0.001f).of(valueInBToCWithInterruption)
+        assertThat(lastValues[SceneB]).isWithin(0.001f).of(valueInBToCWithInterruption)
+        assertThat(lastValues[SceneC]).isWithin(0.001f).of(valueInBToCWithInterruption)
+
         // Finish the transition and interruption.
         bToCProgress = 1f
         interruptionProgress = 0f
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 3751a22..08532bd 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -461,4 +461,30 @@
         assertThat(exception).hasMessageThat().contains(Back.toString())
         assertThat(exception).hasMessageThat().contains(SceneA.debugName)
     }
+
+    @Test
+    fun sceneKeyInScope() {
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+
+        var keyInA: SceneKey? = null
+        var keyInB: SceneKey? = null
+        var keyInC: SceneKey? = null
+        rule.setContent {
+            SceneTransitionLayout(state) {
+                scene(SceneA) { keyInA = sceneKey }
+                scene(SceneB) { keyInB = sceneKey }
+                scene(SceneC) { keyInC = sceneKey }
+            }
+        }
+
+        // Snap to B then C to compose these scenes at least once.
+        rule.runOnUiThread { state.snapToScene(SceneB) }
+        rule.waitForIdle()
+        rule.runOnUiThread { state.snapToScene(SceneC) }
+        rule.waitForIdle()
+
+        assertThat(keyInA).isEqualTo(SceneA)
+        assertThat(keyInB).isEqualTo(SceneB)
+        assertThat(keyInC).isEqualTo(SceneC)
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 3a806a4..25ea2ee 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -30,6 +30,7 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onRoot
 import androidx.compose.ui.test.performTouchInput
@@ -594,4 +595,43 @@
         assertThat(transition).hasToScene(SceneB)
         assertThat(transition).hasProgress(0.5f, tolerance = 0.01f)
     }
+
+    @Test
+    fun overscrollScopeExtendsDensity() {
+        val swipeDistance = 100.dp
+        val state =
+            rule.runOnUiThread {
+                MutableSceneTransitionLayoutState(
+                    SceneA,
+                    transitions {
+                        from(SceneA, to = SceneB) { distance = FixedDistance(swipeDistance) }
+
+                        overscroll(SceneB, Orientation.Vertical) {
+                            translate(TestElements.Foo, x = { 20.dp.toPx() }, y = { 30.dp.toPx() })
+                        }
+                    }
+                )
+            }
+        val layoutSize = 200.dp
+        var touchSlop = 0f
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+                scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+                    Box(Modifier.fillMaxSize())
+                }
+                scene(SceneB) { Box(Modifier.element(TestElements.Foo).fillMaxSize()) }
+            }
+        }
+
+        // Swipe down by twice the swipe distance so that we are at 100% overscrolling on scene B.
+        rule.onRoot().performTouchInput {
+            val middle = (layoutSize / 2).toPx()
+            down(Offset(middle, middle))
+            moveBy(Offset(0f, touchSlop + (swipeDistance * 2).toPx()), delayMillis = 1_000)
+        }
+
+        // Foo should be translated by (20dp, 30dp).
+        rule.onNode(isElement(TestElements.Foo)).assertPositionInRootIsEqualTo(20.dp, 30.dp)
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
index a609be4..e6fa69d 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
@@ -41,8 +41,10 @@
     return object : TransitionState.Transition(from, to), TransitionState.HasOverscrollProperties {
         override val currentScene: SceneKey
             get() = current()
+
         override val progress: Float
             get() = progress()
+
         override val progressVelocity: Float
             get() = progressVelocity()
 
@@ -53,6 +55,8 @@
         override val orientation: Orientation = orientation
         override val overscrollScope: OverscrollScope =
             object : OverscrollScope {
+                override val density: Float = 1f
+                override val fontScale: Float = 1f
                 override val absoluteDistance = 0f
             }
 
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 89c5495..fb2b33d 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
@@ -34,6 +34,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.broadcastDispatcher
 import com.android.systemui.communal.data.model.DisabledReason
+import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_BACKGROUND_SETTING
+import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
 import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -43,6 +45,7 @@
 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
@@ -216,6 +219,32 @@
                 )
         }
 
+    @Test
+    fun backgroundType_defaultValue() =
+        testScope.runTest {
+            val backgroundType by collectLastValue(underTest.getBackground(PRIMARY_USER))
+            assertThat(backgroundType).isEqualTo(CommunalBackgroundType.DEFAULT)
+        }
+
+    @Test
+    fun backgroundType_verifyAllValues() =
+        testScope.runTest {
+            val backgroundType by collectLastValue(underTest.getBackground(PRIMARY_USER))
+            for (type in CommunalBackgroundType.entries) {
+                kosmos.fakeSettings.putIntForUser(
+                    GLANCEABLE_HUB_BACKGROUND_SETTING,
+                    type.value,
+                    PRIMARY_USER.id
+                )
+                assertWithMessage(
+                        "Expected $type when $GLANCEABLE_HUB_BACKGROUND_SETTING is set to" +
+                            " ${type.value} but was $backgroundType"
+                    )
+                    .that(backgroundType)
+                    .isEqualTo(type)
+            }
+        }
+
     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/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index a1bad39..9dcea82 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -37,6 +37,7 @@
 import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
 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.domain.interactor.communalTutorialInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalScenes
@@ -152,6 +153,7 @@
                 kosmos.keyguardInteractor,
                 kosmos.communalSceneInteractor,
                 kosmos.communalInteractor,
+                kosmos.communalSettingsInteractor,
                 kosmos.communalTutorialInteractor,
                 kosmos.shadeInteractor,
                 mediaHost,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 5068f68..78a1167 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -350,7 +350,7 @@
         }
 
     @Test
-    fun quickAffordance_doNotSendUpdatesWhileShadeExpandingAndStillHidden() =
+    fun quickAffordance_updateOncePerShadeExpansion() =
         testScope.runTest {
             val shadeExpansion = MutableStateFlow(0f)
             whenever(shadeInteractor.anyExpansion).thenReturn(shadeExpansion)
@@ -365,9 +365,7 @@
                 shadeExpansion.value = i / 10f
             }
 
-            assertThat(collectedValue[0])
-                .isInstanceOf(KeyguardQuickAffordanceModel.Hidden::class.java)
-            assertThat(collectedValue.size).isEqualTo(initialSize)
+            assertThat(collectedValue.size).isEqualTo(initialSize + 1)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
index b83b93b..10a4eb7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
@@ -50,6 +50,8 @@
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
 
+private const val builtInDeviceName = "This phone"
+
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 @SmallTest
@@ -71,6 +73,10 @@
                 addOverride(R.drawable.ic_media_speaker_device, testIcon)
 
                 addOverride(com.android.internal.R.drawable.ic_bt_hearing_aid, testIcon)
+
+                addOverride(R.string.media_transfer_this_device_name_tv, builtInDeviceName)
+                addOverride(R.string.media_transfer_this_device_name_tablet, builtInDeviceName)
+                addOverride(R.string.media_transfer_this_device_name, builtInDeviceName)
             }
         }
     }
@@ -90,7 +96,7 @@
 
                 assertThat(device).isInstanceOf(AudioOutputDevice.BuiltIn::class.java)
                 assertThat(device!!.icon).isEqualTo(testIcon)
-                assertThat(device!!.name).isEqualTo("built_in")
+                assertThat(device!!.name).isEqualTo(builtInDeviceName)
             }
         }
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt
new file mode 100644
index 0000000..8921a23
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
+
+import android.graphics.drawable.TestStubDrawable
+import android.media.AudioManager
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.data.repository.TestAudioDevicesFactory
+import com.android.systemui.volume.data.repository.audioRepository
+import com.android.systemui.volume.data.repository.audioSharingRepository
+import com.android.systemui.volume.domain.model.AudioOutputDevice
+import com.android.systemui.volume.localMediaController
+import com.android.systemui.volume.localMediaRepository
+import com.android.systemui.volume.mediaControllerRepository
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaOutputComponentModel
+import com.android.systemui.volume.panel.shared.model.filterData
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val builtInDeviceName = "This phone"
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class MediaOutputComponentInteractorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private lateinit var underTest: MediaOutputComponentInteractor
+
+    @Before
+    fun setUp() =
+        with(kosmos) {
+            audioRepository.setMode(AudioManager.MODE_NORMAL)
+            localMediaRepository.updateCurrentConnectedDevice(
+                TestMediaDevicesFactory.builtInMediaDevice(deviceIcon = testIcon)
+            )
+
+            with(context.orCreateTestableResources) {
+                addOverride(R.drawable.ic_smartphone, testIcon)
+
+                addOverride(R.string.media_transfer_this_device_name_tv, builtInDeviceName)
+                addOverride(R.string.media_transfer_this_device_name_tablet, builtInDeviceName)
+                addOverride(R.string.media_transfer_this_device_name, builtInDeviceName)
+            }
+
+            underTest = mediaOutputComponentInteractor
+        }
+
+    @Test
+    fun inCall_stateIs_Calling() =
+        with(kosmos) {
+            testScope.runTest {
+                with(audioRepository) {
+                    setMode(AudioManager.MODE_IN_CALL)
+                    setCommunicationDevice(TestAudioDevicesFactory.builtInDevice())
+                }
+
+                val model by collectLastValue(underTest.mediaOutputModel.filterData())
+                runCurrent()
+
+                assertThat(model)
+                    .isEqualTo(
+                        MediaOutputComponentModel.Calling(
+                            AudioOutputDevice.BuiltIn(builtInDeviceName, testIcon),
+                            false,
+                        )
+                    )
+            }
+        }
+
+    @Test
+    fun hasSession_stateIs_MediaSession() =
+        with(kosmos) {
+            testScope.runTest {
+                mediaControllerRepository.setActiveSessions(listOf(localMediaController))
+
+                val model by collectLastValue(underTest.mediaOutputModel.filterData())
+                runCurrent()
+
+                with(model as MediaOutputComponentModel.MediaSession) {
+                    assertThat(session.appLabel).isEqualTo("local_media_controller_label")
+                    assertThat(session.packageName).isEqualTo("local.test.pkg")
+                    assertThat(session.canAdjustVolume).isTrue()
+                    assertThat(device)
+                        .isEqualTo(AudioOutputDevice.BuiltIn("built_in_media", testIcon))
+                    assertThat(isInAudioSharing).isFalse()
+                }
+            }
+        }
+
+    @Test
+    fun noMediaOrCall_stateIs_Idle() =
+        with(kosmos) {
+            testScope.runTest {
+                audioSharingRepository.setInAudioSharing(true)
+
+                val model by collectLastValue(underTest.mediaOutputModel.filterData())
+                runCurrent()
+
+                assertThat(model)
+                    .isEqualTo(
+                        MediaOutputComponentModel.Idle(
+                            AudioOutputDevice.BuiltIn("built_in_media", testIcon),
+                            true,
+                        )
+                    )
+            }
+        }
+
+    private companion object {
+        val testIcon = TestStubDrawable()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
index d497b4a..86a20dc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
@@ -31,14 +31,11 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.volume.data.repository.audioSharingRepository
-import com.android.systemui.volume.domain.interactor.audioModeInteractor
-import com.android.systemui.volume.domain.interactor.audioOutputInteractor
 import com.android.systemui.volume.localMediaController
 import com.android.systemui.volume.localMediaRepository
 import com.android.systemui.volume.mediaControllerRepository
-import com.android.systemui.volume.mediaDeviceSessionInteractor
 import com.android.systemui.volume.mediaOutputActionsInteractor
-import com.android.systemui.volume.mediaOutputInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.mediaOutputComponentInteractor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
@@ -66,10 +63,7 @@
                     applicationContext,
                     testScope.backgroundScope,
                     mediaOutputActionsInteractor,
-                    mediaDeviceSessionInteractor,
-                    audioOutputInteractor,
-                    audioModeInteractor,
-                    mediaOutputInteractor,
+                    mediaOutputComponentInteractor,
                     uiEventLogger,
                 )
 
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_spatial_speaker.xml b/packages/SystemUI/res-keyguard/drawable/ic_spatial_speaker.xml
new file mode 100644
index 0000000..82b222e7
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_spatial_speaker.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M360,880q-134,0 -227,-93T40,560h80q0,100 70,170t170,70v80ZM360,740q-75,0 -127.5,-52.5T180,560h80q0,42 29,71t71,29v80ZM400,600q-33,0 -56.5,-23.5T320,520v-320q0,-33 23.5,-56.5T400,120h160q33,0 56.5,23.5T640,200v320q0,33 -23.5,56.5T560,600L400,600ZM400,520h160v-320L400,200v320ZM600,740v-80q42,0 71,-29t29,-71h80q0,75 -52.5,127.5T600,740ZM600,880v-80q100,0 170,-70t70,-170h80q0,134 -93,227T600,880ZM400,520h160,-160Z" />
+</vector>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 47e4b49..f688d4f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -308,14 +308,14 @@
         mStatusArea = mView.findViewById(R.id.keyguard_status_area);
 
         mBgExecutor.execute(() -> {
-            mSecureSettings.registerContentObserverForUser(
+            mSecureSettings.registerContentObserverForUserSync(
                     Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
                     false, /* notifyForDescendants */
                     mDoubleLineClockObserver,
                     UserHandle.USER_ALL
             );
 
-            mSecureSettings.registerContentObserverForUser(
+            mSecureSettings.registerContentObserverForUserSync(
                     Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED,
                     false, /* notifyForDescendants */
                     mShowWeatherObserver,
@@ -372,8 +372,8 @@
         setClock(null);
 
         mBgExecutor.execute(() -> {
-            mSecureSettings.unregisterContentObserver(mDoubleLineClockObserver);
-            mSecureSettings.unregisterContentObserver(mShowWeatherObserver);
+            mSecureSettings.unregisterContentObserverSync(mDoubleLineClockObserver);
+            mSecureSettings.unregisterContentObserverSync(mShowWeatherObserver);
         });
 
         mKeyguardUnlockAnimationController.removeKeyguardUnlockAnimationListener(
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index ca24ccb..f2a68a8 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -319,7 +319,7 @@
         }
 
         // Unregister observer before removing view
-        mSecureSettings.unregisterContentObserver(mMagnificationCapabilityObserver);
+        mSecureSettings.unregisterContentObserverSync(mMagnificationCapabilityObserver);
         mWindowManager.removeView(mSettingView);
         mIsVisible = false;
         if (resetPosition) {
@@ -380,7 +380,7 @@
 
             mWindowManager.addView(mSettingView, mParams);
 
-            mSecureSettings.registerContentObserverForUser(
+            mSecureSettings.registerContentObserverForUserSync(
                     Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
                     mMagnificationCapabilityObserver,
                     UserHandle.USER_CURRENT);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
index eb840f1..ffb5f3d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
@@ -240,26 +240,26 @@
     }
 
     void registerObserversAndCallbacks() {
-        mSecureSettings.registerContentObserverForUser(
+        mSecureSettings.registerContentObserverForUserSync(
                 mSecureSettings.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
                 /* notifyForDescendants */ false, mMenuTargetFeaturesContentObserver,
                 UserHandle.USER_CURRENT);
         if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) {
-            mSecureSettings.registerContentObserverForUser(
+            mSecureSettings.registerContentObserverForUserSync(
                     mSecureSettings.getUriFor(ENABLED_ACCESSIBILITY_SERVICES),
                     /* notifyForDescendants */ false,
                     mMenuTargetFeaturesContentObserver,
                     UserHandle.USER_CURRENT);
         }
-        mSecureSettings.registerContentObserverForUser(
+        mSecureSettings.registerContentObserverForUserSync(
                 mSecureSettings.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE),
                 /* notifyForDescendants */ false, mMenuSizeContentObserver,
                 UserHandle.USER_CURRENT);
-        mSecureSettings.registerContentObserverForUser(
+        mSecureSettings.registerContentObserverForUserSync(
                 mSecureSettings.getUriFor(ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED),
                 /* notifyForDescendants */ false, mMenuFadeOutContentObserver,
                 UserHandle.USER_CURRENT);
-        mSecureSettings.registerContentObserverForUser(
+        mSecureSettings.registerContentObserverForUserSync(
                 mSecureSettings.getUriFor(ACCESSIBILITY_FLOATING_MENU_OPACITY),
                 /* notifyForDescendants */ false, mMenuFadeOutContentObserver,
                 UserHandle.USER_CURRENT);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt
index 91bc0c1..eaf541d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt
@@ -29,12 +29,12 @@
 import android.widget.TextView
 import androidx.annotation.MainThread
 import androidx.annotation.WorkerThread
-import com.android.systemui.res.R
 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener
 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener.ControlUnitType
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -46,7 +46,9 @@
 import kotlin.math.roundToInt
 
 /** The Dialog that contains a seekbar for changing the font size. */
-class FontScalingDialogDelegate @Inject constructor(
+class FontScalingDialogDelegate
+@Inject
+constructor(
     private val context: Context,
     private val systemUIDialogFactory: SystemUIDialog.Factory,
     private val layoutInflater: LayoutInflater,
@@ -84,9 +86,9 @@
         dialog.setTitle(R.string.font_scaling_dialog_title)
         dialog.setView(layoutInflater.inflate(R.layout.font_scaling_dialog, null))
         dialog.setPositiveButton(
-                R.string.quick_settings_done,
-                /* onClick = */ null,
-                /* dismissOnClick = */ true
+            R.string.quick_settings_done,
+            /* onClick = */ null,
+            /* dismissOnClick = */ true
         )
     }
 
@@ -142,7 +144,7 @@
             }
         )
         doneButton.setOnClickListener { dialog.dismiss() }
-        systemSettings.registerContentObserver(Settings.System.FONT_SCALE, fontSizeObserver)
+        systemSettings.registerContentObserverSync(Settings.System.FONT_SCALE, fontSizeObserver)
     }
 
     /**
@@ -165,7 +167,7 @@
     override fun onStop(dialog: SystemUIDialog) {
         cancelUpdateFontScaleRunnable?.run()
         cancelUpdateFontScaleRunnable = null
-        systemSettings.unregisterContentObserver(fontSizeObserver)
+        systemSettings.unregisterContentObserverSync(fontSizeObserver)
     }
 
     @MainThread
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt
index 68c4a10..2970890 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt
@@ -75,7 +75,7 @@
 ) {
     fun fetch(): Boolean = getIntForUser(key, if (defaultValue) 1 else 0, userId) > 0
 
-    registerContentObserverForUser(
+    registerContentObserverForUserSync(
         key,
         false /* notifyForDescendants */,
         object : ContentObserver(handler) {
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 88cb64c..1c47e50 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
@@ -30,6 +30,7 @@
 import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_FLAG
 import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_INVALID_USER
 import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_USER_SETTING
+import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.flags.FeatureFlagsClassic
@@ -59,6 +60,9 @@
 
     /** Keyguard widgets enabled state by Device Policy Manager for the specified user. */
     fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean>
+
+    /** The type of background to use for the hub. Used to experiment with different backgrounds. */
+    fun getBackground(user: UserInfo): Flow<CommunalBackgroundType>
 }
 
 @SysUISingleton
@@ -126,6 +130,21 @@
             .emitOnStart()
             .map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) }
 
+    override fun getBackground(user: UserInfo): Flow<CommunalBackgroundType> =
+        secureSettings
+            .observerFlow(userId = user.id, names = arrayOf(GLANCEABLE_HUB_BACKGROUND_SETTING))
+            .emitOnStart()
+            .map {
+                val intType =
+                    secureSettings.getIntForUser(
+                        GLANCEABLE_HUB_BACKGROUND_SETTING,
+                        CommunalBackgroundType.DEFAULT.value,
+                        user.id
+                    )
+                CommunalBackgroundType.entries.find { type -> type.value == intType }
+                    ?: CommunalBackgroundType.DEFAULT
+            }
+
     private fun getEnabledByUser(user: UserInfo): Flow<Boolean> =
         secureSettings
             .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.GLANCEABLE_HUB_ENABLED))
@@ -141,6 +160,7 @@
 
     companion object {
         const val GLANCEABLE_HUB_CONTENT_SETTING = "glanceable_hub_content_setting"
+        const val GLANCEABLE_HUB_BACKGROUND_SETTING = "glanceable_hub_background"
         private const val ENABLED_SETTING_DEFAULT = 1
     }
 }
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 3e5126a..f043d58 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.model.CommunalWidgetCategories
 import com.android.systemui.communal.data.repository.CommunalSettingsRepository
+import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.log.dagger.CommunalTableLog
@@ -30,6 +31,7 @@
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import java.util.concurrent.Executor
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
@@ -38,6 +40,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
@@ -47,6 +50,7 @@
 @Inject
 constructor(
     @Background private val bgScope: CoroutineScope,
+    @Background private val bgDispatcher: CoroutineDispatcher,
     @Background private val bgExecutor: Executor,
     private val repository: CommunalSettingsRepository,
     userInteractor: SelectedUserInteractor,
@@ -78,6 +82,12 @@
                 initialValue = CommunalWidgetCategories.defaultCategories
             )
 
+    /** The type of background to use for the hub. Used to experiment with different backgrounds */
+    val communalBackground: Flow<CommunalBackgroundType> =
+        userInteractor.selectedUserInfo
+            .flatMapLatest { user -> repository.getBackground(user) }
+            .flowOn(bgDispatcher)
+
     private val workProfileUserInfoCallbackFlow: Flow<UserInfo?> = conflatedCallbackFlow {
         fun send(profiles: List<UserInfo>) {
             trySend(profiles.find { it.isManagedProfile })
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt
new file mode 100644
index 0000000..8b816db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.shared.model
+
+/** Models the types of background that can be shown on the hub. */
+enum class CommunalBackgroundType(val value: Int) {
+    DEFAULT(0),
+    STATIC_GRADIENT(1),
+    ANIMATED(2),
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index ce69ee8..3e00b04 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -21,8 +21,10 @@
 import android.view.accessibility.AccessibilityNodeInfo
 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.domain.interactor.CommunalTutorialInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
@@ -70,6 +72,7 @@
     keyguardInteractor: KeyguardInteractor,
     communalSceneInteractor: CommunalSceneInteractor,
     private val communalInteractor: CommunalInteractor,
+    private val communalSettingsInteractor: CommunalSettingsInteractor,
     tutorialInteractor: CommunalTutorialInteractor,
     private val shadeInteractor: ShadeInteractor,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
@@ -284,6 +287,10 @@
      */
     val showGestureIndicator: Flow<Boolean> = not(keyguardInteractor.isDreaming)
 
+    /** The type of background to use for the hub. */
+    val communalBackground: Flow<CommunalBackgroundType> =
+        communalSettingsInteractor.communalBackground
+
     companion object {
         const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L
     }
@@ -291,5 +298,6 @@
 
 sealed class PopupType {
     object CtaTile : PopupType()
+
     object CustomizeWidgetButton : PopupType()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java
index 0bdc7f1..84807fb 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java
@@ -69,15 +69,15 @@
             }
         };
 
-        mSecureSettings.registerContentObserverForUser(
+        mSecureSettings.registerContentObserverForUserSync(
                 Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED,
                 settingsObserver,
                 UserHandle.myUserId());
-        mSecureSettings.registerContentObserverForUser(
+        mSecureSettings.registerContentObserverForUserSync(
                 Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED,
                 settingsObserver,
                 UserHandle.myUserId());
-        mSecureSettings.registerContentObserverForUser(
+        mSecureSettings.registerContentObserverForUserSync(
                 Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
                 settingsObserver,
                 UserHandle.myUserId());
diff --git a/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt b/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt
index 1bbdfcd..4dbb32d 100644
--- a/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt
+++ b/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt
@@ -19,18 +19,16 @@
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
 
 /**
- * Retrieves whether or not the device is docked according to DockManager. Emits a starting value of
- * isDocked.
+ * Retrieves whether or not the device is docked according to DockManager. Emits a starting value
+ *  of isDocked.
  */
 fun DockManager.retrieveIsDocked(): Flow<Boolean> =
     ConflatedCallbackFlow.conflatedCallbackFlow {
-            val callback = DockManager.DockEventListener { trySend(isDocked) }
-            addListener(callback)
-            trySend(isDocked)
+        val callback = DockManager.DockEventListener { trySend(isDocked) }
+        addListener(callback)
+        trySend(isDocked)
 
-            awaitClose { removeListener(callback) }
-        }
-        .distinctUntilChanged()
+        awaitClose { removeListener(callback) }
+    }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 3194942..7ae8409 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -426,7 +426,7 @@
         }
 
         if (!anyListening) {
-            mSecureSettings.unregisterContentObserver(mSettingsObserver);
+            mSecureSettings.unregisterContentObserverSync(mSettingsObserver);
         } else if (!mSettingRegistered) {
             for (TriggerSensor s : mTriggerSensors) {
                 s.registerSettingsObserver(mSettingsObserver);
@@ -750,7 +750,7 @@
 
         public void registerSettingsObserver(ContentObserver settingsObserver) {
             if (mConfigured && !TextUtils.isEmpty(mSetting)) {
-                mSecureSettings.registerContentObserverForUser(
+                mSecureSettings.registerContentObserverForUserSync(
                         mSetting, mSettingsObserver, UserHandle.USER_ALL);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 49be03c..1e4fb4f 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -428,7 +428,7 @@
 
         // get notified of phone state changes
         mTelephonyListenerManager.addServiceStateListener(mPhoneStateListener);
-        mGlobalSettings.registerContentObserver(
+        mGlobalSettings.registerContentObserverSync(
                 Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
                 mAirplaneModeObserver);
         mHasVibrator = vibrator.hasVibrator();
@@ -453,7 +453,7 @@
     public void destroy() {
         mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
         mTelephonyListenerManager.removeServiceStateListener(mPhoneStateListener);
-        mGlobalSettings.unregisterContentObserver(mAirplaneModeObserver);
+        mGlobalSettings.unregisterContentObserverSync(mAirplaneModeObserver);
         mConfigurationController.removeCallback(this);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index f488d3b..8ec460a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -265,7 +265,7 @@
         state: TransitionState
     ) {
         if (updateTransitionId != transitionId) {
-            Log.w(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
+            Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
             return
         }
 
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 8ffa4bb..ccce3bf 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
@@ -105,35 +105,33 @@
         }
 
         return combine(
-                quickAffordanceAlwaysVisible(position),
-                keyguardInteractor.isDozing,
-                if (SceneContainerFlag.isEnabled) {
-                    sceneInteractor
-                        .get()
-                        .transitionState
-                        .map {
-                            when (it) {
-                                is ObservableTransitionState.Idle ->
-                                    it.currentScene == Scenes.Lockscreen
-                                is ObservableTransitionState.Transition ->
-                                    it.fromScene == Scenes.Lockscreen ||
-                                        it.toScene == Scenes.Lockscreen
-                            }
+            quickAffordanceAlwaysVisible(position),
+            keyguardInteractor.isDozing,
+            if (SceneContainerFlag.isEnabled) {
+                sceneInteractor
+                    .get()
+                    .transitionState
+                    .map {
+                        when (it) {
+                            is ObservableTransitionState.Idle ->
+                                it.currentScene == Scenes.Lockscreen
+                            is ObservableTransitionState.Transition ->
+                                it.fromScene == Scenes.Lockscreen || it.toScene == Scenes.Lockscreen
                         }
-                        .distinctUntilChanged()
-                } else {
-                    keyguardInteractor.isKeyguardShowing
-                },
-                shadeInteractor.anyExpansion.map { it < 1.0f }.distinctUntilChanged(),
-                biometricSettingsRepository.isCurrentUserInLockdown,
-            ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown ->
-                if (!isDozing && isKeyguardShowing && isQuickSettingsVisible && !isUserInLockdown) {
-                    affordance
-                } else {
-                    KeyguardQuickAffordanceModel.Hidden
-                }
+                    }
+                    .distinctUntilChanged()
+            } else {
+                keyguardInteractor.isKeyguardShowing
+            },
+            shadeInteractor.anyExpansion.map { it < 1.0f }.distinctUntilChanged(),
+            biometricSettingsRepository.isCurrentUserInLockdown,
+        ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown ->
+            if (!isDozing && isKeyguardShowing && isQuickSettingsVisible && !isUserInLockdown) {
+                affordance
+            } else {
+                KeyguardQuickAffordanceModel.Hidden
             }
-            .distinctUntilChanged()
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt
index 6d579f3..f5b82cf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt
@@ -40,6 +40,7 @@
         rebuildSections: List<KeyguardSection> = listOf(),
         bindData: Boolean = true
     ) {
+        rebuildSections.forEach { it.onRebuildBegin() }
         val prevSections = previousBlueprint?.sections ?: listOf()
         val skipSections = sections.intersect(prevSections).subtract(rebuildSections)
         prevSections.subtract(skipSections).forEach { it.removeViews(constraintLayout) }
@@ -49,6 +50,7 @@
                 it.bindData(constraintLayout)
             }
         }
+        rebuildSections.forEach { it.onRebuildEnd() }
     }
 
     /** Rebuilds views for the target sections, or all of them if unspecified. */
@@ -61,6 +63,7 @@
             return
         }
 
+        rebuildSections.forEach { it.onRebuildBegin() }
         rebuildSections.forEach { it.removeViews(constraintLayout) }
         rebuildSections.forEach {
             it.addViews(constraintLayout)
@@ -68,6 +71,7 @@
                 it.bindData(constraintLayout)
             }
         }
+        rebuildSections.forEach { it.onRebuildEnd() }
     }
 
     fun applyConstraints(constraintSet: ConstraintSet) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt
index 48a2146..473b08f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt
@@ -33,6 +33,12 @@
     /** Removes views and does any data binding destruction. */
     abstract fun removeViews(constraintLayout: ConstraintLayout)
 
+    /* Notifies the section is being rebuilt */
+    open fun onRebuildBegin() {}
+
+    /* Notifies the secion that the rebuild is complete */
+    open fun onRebuildEnd() {}
+
     /**
      * Defines equality as same class.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index 807c322..23c2491 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -65,7 +65,7 @@
         val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
         val disposableHandle =
             view.repeatWhenAttached {
-                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
                     launch("$TAG#viewModel.alpha") {
                         // Do not independently apply alpha, as [KeyguardRootViewModel] should work
                         // for this and all its children
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index 1cf009d..b9a79dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -30,7 +30,6 @@
 import androidx.core.view.updateLayoutParams
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.tracing.coroutines.launch
 import com.android.settingslib.Utils
 import com.android.systemui.animation.Expandable
 import com.android.systemui.animation.view.LaunchableImageView
@@ -81,8 +80,8 @@
         val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
         val disposableHandle =
             view.repeatWhenAttached {
-                repeatOnLifecycle(Lifecycle.State.CREATED) {
-                    launch("$TAG#viewModel.collect") {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    launch {
                         viewModel.collect { buttonModel ->
                             updateButton(
                                 view = button,
@@ -94,7 +93,7 @@
                         }
                     }
 
-                    launch("$TAG#updateButtonAlpha") {
+                    launch {
                         updateButtonAlpha(
                             view = button,
                             viewModel = viewModel,
@@ -102,7 +101,7 @@
                         )
                     }
 
-                    launch("$TAG#configurationBasedDimensions") {
+                    launch {
                         configurationBasedDimensions.collect { dimensions ->
                             button.updateLayoutParams<ViewGroup.LayoutParams> {
                                 width = dimensions.buttonSizePx.width
@@ -324,6 +323,4 @@
     private data class ConfigurationBasedDimensions(
         val buttonSizePx: Size,
     )
-
-    private const val TAG = "KeyguardQuickAffordanceViewBinder"
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index 2d6690f..0435531 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -56,6 +56,14 @@
     private var smartspaceVisibilityListener: OnGlobalLayoutListener? = null
     private var pastVisibility: Int = -1
 
+    override fun onRebuildBegin() {
+        smartspaceController.suppressDisconnects = true
+    }
+
+    override fun onRebuildEnd() {
+        smartspaceController.suppressDisconnects = false
+    }
+
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (!MigrateClocksToBlueprint.isEnabled) return
         if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 244d842..c4383fc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -18,7 +18,6 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import androidx.annotation.VisibleForTesting
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -29,23 +28,19 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.stateIn
 
 @OptIn(ExperimentalCoroutinesApi::class)
 class KeyguardQuickAffordancesCombinedViewModel
 @Inject
 constructor(
-    @Application applicationScope: CoroutineScope,
     private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
     private val keyguardInteractor: KeyguardInteractor,
     shadeInteractor: ShadeInteractor,
@@ -89,20 +84,15 @@
     /** The only time the expansion is important is while lockscreen is actively displayed */
     private val shadeExpansionAlpha =
         combine(
-                showingLockscreen,
-                shadeInteractor.anyExpansion,
-            ) { showingLockscreen, expansion ->
-                if (showingLockscreen) {
-                    1 - expansion
-                } else {
-                    0f
-                }
+            showingLockscreen,
+            shadeInteractor.anyExpansion,
+        ) { showingLockscreen, expansion ->
+            if (showingLockscreen) {
+                1 - expansion
+            } else {
+                0f
             }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.Lazily,
-                initialValue = 0f,
-            )
+        }
 
     /**
      * ID of the slot that's currently selected in the preview that renders exclusively in the
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 7bacdeb..edead51 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -397,7 +397,7 @@
         listenForLockscreenSettingChanges(applicationScope)
 
         // Notifies all active players about animation scale changes.
-        globalSettings.registerContentObserver(
+        globalSettings.registerContentObserverSync(
             Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
             animationScaleObserver
         )
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
index 6589038..601d563 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
@@ -167,6 +167,7 @@
     private var targetBounds: Rect = Rect()
     private val mediaFrame
         get() = mediaCarouselController.mediaFrame
+
     private var statusbarState: Int = statusBarStateController.state
     private var animator =
         ValueAnimator.ofFloat(0.0f, 1.0f).apply {
@@ -600,7 +601,7 @@
                     }
                 }
             }
-        secureSettings.registerContentObserverForUser(
+        secureSettings.registerContentObserverForUserSync(
             Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
             settingsObserver,
             UserHandle.USER_ALL
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
index 2d460a0..765b45b 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
@@ -341,7 +341,7 @@
         }
 
         mQRCodeScannerPreferenceObserver.forEach((key, value) -> {
-            mSecureSettings.unregisterContentObserver(value);
+            mSecureSettings.unregisterContentObserverSync(value);
         });
 
         // Reset cached values to default as we are no longer listening
@@ -418,7 +418,7 @@
                 });
             }
         });
-        mSecureSettings.registerContentObserverForUser(
+        mSecureSettings.registerContentObserverForUserSync(
                 mSecureSettings.getUriFor(LOCK_SCREEN_SHOW_QR_CODE_SCANNER), false,
                 mQRCodeScannerPreferenceObserver.get(userId), userId);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
index 2fafba1..e4bafcd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
@@ -52,7 +52,9 @@
  *
  * It also handles restore gracefully.
  */
-class AutoAddTracker @VisibleForTesting constructor(
+class AutoAddTracker
+@VisibleForTesting
+constructor(
     private val secureSettings: SecureSettings,
     private val broadcastDispatcher: BroadcastDispatcher,
     private val qsHost: QSHost,
@@ -66,39 +68,43 @@
         private val FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED)
     }
 
-    @GuardedBy("autoAdded")
-    private val autoAdded = ArraySet<String>()
+    @GuardedBy("autoAdded") private val autoAdded = ArraySet<String>()
     private var restoredTiles: Map<String, AutoTile>? = null
 
     override val currentUserId: Int
         get() = userId
 
-    private val contentObserver = object : ContentObserver(mainHandler) {
-        override fun onChange(
-            selfChange: Boolean,
-            uris: Collection<Uri>,
-            flags: Int,
-            _userId: Int
-        ) {
-            if (_userId != userId) {
-                // Ignore changes outside of our user. We'll load the correct value on user change
-                return
+    private val contentObserver =
+        object : ContentObserver(mainHandler) {
+            override fun onChange(
+                selfChange: Boolean,
+                uris: Collection<Uri>,
+                flags: Int,
+                _userId: Int
+            ) {
+                if (_userId != userId) {
+                    // Ignore changes outside of our user. We'll load the correct value on user
+                    // change
+                    return
+                }
+                loadTiles()
             }
-            loadTiles()
         }
-    }
 
-    private val restoreReceiver = object : BroadcastReceiver() {
-        override fun onReceive(context: Context, intent: Intent) {
-            if (intent.action != Intent.ACTION_SETTING_RESTORED) return
-            processRestoreIntent(intent)
+    private val restoreReceiver =
+        object : BroadcastReceiver() {
+            override fun onReceive(context: Context, intent: Intent) {
+                if (intent.action != Intent.ACTION_SETTING_RESTORED) return
+                processRestoreIntent(intent)
+            }
         }
-    }
 
     private fun processRestoreIntent(intent: Intent) {
         when (intent.getStringExtra(Intent.EXTRA_SETTING_NAME)) {
             Settings.Secure.QS_TILES -> {
-                restoredTiles = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
+                restoredTiles =
+                    intent
+                        .getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
                         ?.split(DELIMITER)
                         ?.mapIndexed(::AutoTile)
                         ?.associateBy(AutoTile::tileType)
@@ -109,13 +115,11 @@
             }
             Settings.Secure.QS_AUTO_ADDED_TILES -> {
                 restoredTiles?.let { restoredTiles ->
-                    val restoredAutoAdded = intent
-                            .getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
-                            ?.split(DELIMITER)
+                    val restoredAutoAdded =
+                        intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)?.split(DELIMITER)
                             ?: emptyList()
-                    val autoAddedBeforeRestore = intent
-                            .getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE)
-                            ?.split(DELIMITER)
+                    val autoAddedBeforeRestore =
+                        intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE)?.split(DELIMITER)
                             ?: emptyList()
 
                     val tilesToRemove = restoredAutoAdded.filter { it !in restoredTiles }
@@ -123,50 +127,51 @@
                         Log.d(TAG, "Removing tiles: $tilesToRemove")
                         qsHost.removeTiles(tilesToRemove)
                     }
-                    val tiles = synchronized(autoAdded) {
-                        autoAdded.clear()
-                        autoAdded.addAll(restoredAutoAdded + autoAddedBeforeRestore)
-                        getTilesFromListLocked()
-                    }
+                    val tiles =
+                        synchronized(autoAdded) {
+                            autoAdded.clear()
+                            autoAdded.addAll(restoredAutoAdded + autoAddedBeforeRestore)
+                            getTilesFromListLocked()
+                        }
                     saveTiles(tiles)
-                } ?: run {
-                    Log.w(TAG, "${Settings.Secure.QS_AUTO_ADDED_TILES} restored before " +
-                            "${Settings.Secure.QS_TILES} for user $userId")
                 }
+                    ?: run {
+                        Log.w(
+                            TAG,
+                            "${Settings.Secure.QS_AUTO_ADDED_TILES} restored before " +
+                                "${Settings.Secure.QS_TILES} for user $userId"
+                        )
+                    }
             }
             else -> {} // Do nothing for other Settings
         }
     }
 
-    /**
-     * Init method must be called after construction to start listening
-     */
+    /** Init method must be called after construction to start listening */
     fun initialize() {
         dumpManager.registerDumpable(TAG, this)
         loadTiles()
-        secureSettings.registerContentObserverForUser(
-                secureSettings.getUriFor(Settings.Secure.QS_AUTO_ADDED_TILES),
-                contentObserver,
-                UserHandle.USER_ALL
+        secureSettings.registerContentObserverForUserSync(
+            secureSettings.getUriFor(Settings.Secure.QS_AUTO_ADDED_TILES),
+            contentObserver,
+            UserHandle.USER_ALL
         )
         registerBroadcastReceiver()
     }
 
-    /**
-     * Unregister listeners, receivers and observers
-     */
+    /** Unregister listeners, receivers and observers */
     fun destroy() {
         dumpManager.unregisterDumpable(TAG)
-        secureSettings.unregisterContentObserver(contentObserver)
+        secureSettings.unregisterContentObserverSync(contentObserver)
         unregisterBroadcastReceiver()
     }
 
     private fun registerBroadcastReceiver() {
         broadcastDispatcher.registerReceiver(
-                restoreReceiver,
-                FILTER,
-                backgroundExecutor,
-                UserHandle.of(userId)
+            restoreReceiver,
+            FILTER,
+            backgroundExecutor,
+            UserHandle.of(userId)
         )
     }
 
@@ -186,13 +191,9 @@
     fun getRestoredTilePosition(tile: String): Int =
         restoredTiles?.get(tile)?.index ?: QSHost.POSITION_AT_END
 
-    /**
-     * Returns `true` if the tile has been auto-added before
-     */
+    /** Returns `true` if the tile has been auto-added before */
     fun isAdded(tile: String): Boolean {
-        return synchronized(autoAdded) {
-            tile in autoAdded
-        }
+        return synchronized(autoAdded) { tile in autoAdded }
     }
 
     /**
@@ -201,13 +202,14 @@
      * From here on, [isAdded] will return true for that tile.
      */
     fun setTileAdded(tile: String) {
-        val tiles = synchronized(autoAdded) {
-            if (autoAdded.add(tile)) {
-                getTilesFromListLocked()
-            } else {
-                null
+        val tiles =
+            synchronized(autoAdded) {
+                if (autoAdded.add(tile)) {
+                    getTilesFromListLocked()
+                } else {
+                    null
+                }
             }
-        }
         tiles?.let { saveTiles(it) }
     }
 
@@ -217,13 +219,14 @@
      * This allows for this tile to be auto-added again in the future.
      */
     fun setTileRemoved(tile: String) {
-        val tiles = synchronized(autoAdded) {
-            if (autoAdded.remove(tile)) {
-                getTilesFromListLocked()
-            } else {
-                null
+        val tiles =
+            synchronized(autoAdded) {
+                if (autoAdded.remove(tile)) {
+                    getTilesFromListLocked()
+                } else {
+                    null
+                }
             }
-        }
         tiles?.let { saveTiles(it) }
     }
 
@@ -233,12 +236,12 @@
 
     private fun saveTiles(tiles: String) {
         secureSettings.putStringForUser(
-                Settings.Secure.QS_AUTO_ADDED_TILES,
-                tiles,
-                /* tag */ null,
-                /* makeDefault */ false,
-                userId,
-                /* overrideableByRestore */ true
+            Settings.Secure.QS_AUTO_ADDED_TILES,
+            tiles,
+            /* tag */ null,
+            /* makeDefault */ false,
+            userId,
+            /* overrideableByRestore */ true
         )
     }
 
@@ -261,7 +264,9 @@
     }
 
     @SysUISingleton
-    class Builder @Inject constructor(
+    class Builder
+    @Inject
+    constructor(
         private val secureSettings: SecureSettings,
         private val broadcastDispatcher: BroadcastDispatcher,
         private val qsHost: QSHost,
@@ -278,16 +283,16 @@
 
         fun build(): AutoAddTracker {
             return AutoAddTracker(
-                    secureSettings,
-                    broadcastDispatcher,
-                    qsHost,
-                    dumpManager,
-                    handler,
-                    executor,
-                    userId
+                secureSettings,
+                broadcastDispatcher,
+                qsHost,
+                dumpManager,
+                handler,
+                executor,
+                userId
             )
         }
     }
 
     private data class AutoTile(val index: Int, val tileType: String)
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
index 4fc6609..846d63f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
@@ -77,8 +77,8 @@
             public void onUserChanged(int newUser, Context userContext) {
                 synchronized (mListeners) {
                     if (mListeners.size() > 0) {
-                        mSecureSettings.unregisterContentObserver(mContentObserver);
-                        mSecureSettings.registerContentObserverForUser(
+                        mSecureSettings.unregisterContentObserverSync(mContentObserver);
+                        mSecureSettings.registerContentObserverForUserSync(
                                 Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
                                 false, mContentObserver, newUser);
                     }
@@ -94,7 +94,7 @@
             if (!mListeners.contains(listener)) {
                 mListeners.add(listener);
                 if (mListeners.size() == 1) {
-                    mSecureSettings.registerContentObserverForUser(
+                    mSecureSettings.registerContentObserverForUserSync(
                             Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
                             false, mContentObserver, mUserTracker.getUserId());
                 }
@@ -106,7 +106,7 @@
     public void removeCallback(@androidx.annotation.NonNull Listener listener) {
         synchronized (mListeners) {
             if (mListeners.remove(listener) && mListeners.size() == 0) {
-                mSecureSettings.unregisterContentObserver(mContentObserver);
+                mSecureSettings.unregisterContentObserverSync(mContentObserver);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
index eb11568..6092348 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
@@ -74,10 +74,10 @@
         mListening = listening;
         if (listening) {
             mObservedValue = getValueFromProvider();
-            mSettingsProxy.registerContentObserver(
+            mSettingsProxy.registerContentObserverSync(
                     mSettingsProxy.getUriFor(mSettingName), false, this);
         } else {
-            mSettingsProxy.unregisterContentObserver(this);
+            mSettingsProxy.unregisterContentObserverSync(this);
             mObservedValue = mDefaultValue;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java
index 539c2d6..1b34c33 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java
@@ -76,10 +76,10 @@
         mListening = listening;
         if (listening) {
             mObservedValue = getValueFromProvider();
-            mSettingsProxy.registerContentObserverForUser(
+            mSettingsProxy.registerContentObserverForUserSync(
                     mSettingsProxy.getUriFor(mSettingName), false, this, mUserId);
         } else {
-            mSettingsProxy.unregisterContentObserver(this);
+            mSettingsProxy.unregisterContentObserverSync(this);
             mObservedValue = mDefaultValue;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt
index d452241..9fcb0db 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt
@@ -44,9 +44,8 @@
     @Background private val bgDispatcher: CoroutineDispatcher,
 ) {
 
-    private val changeEvents = MutableSharedFlow<ChangeAction>(
-        extraBufferCapacity = CHANGES_BUFFER_SIZE
-    )
+    private val changeEvents =
+        MutableSharedFlow<ChangeAction>(extraBufferCapacity = CHANGES_BUFFER_SIZE)
 
     private lateinit var _autoAdded: StateFlow<Set<TileSpec>>
 
@@ -85,8 +84,8 @@
                                     trySend(Unit)
                                 }
                             }
-                        secureSettings.registerContentObserverForUser(SETTING, observer, userId)
-                        awaitClose { secureSettings.unregisterContentObserver(observer) }
+                        secureSettings.registerContentObserverForUserSync(SETTING, observer, userId)
+                        awaitClose { secureSettings.unregisterContentObserverSync(observer) }
                     }
                     .map { load() }
                     .flowOn(bgDispatcher)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
index aca8733..1f9570a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
@@ -98,8 +98,8 @@
                                     trySend(Unit)
                                 }
                             }
-                        secureSettings.registerContentObserverForUser(SETTING, observer, userId)
-                        awaitClose { secureSettings.unregisterContentObserver(observer) }
+                        secureSettings.registerContentObserverForUserSync(SETTING, observer, userId)
+                        awaitClose { secureSettings.unregisterContentObserverSync(observer) }
                     }
                     .map { loadTilesFromSettings(userId) }
                     .flowOn(backgroundDispatcher)
diff --git a/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt b/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt
index 3c0aa38..09fd7df 100644
--- a/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt
@@ -66,9 +66,9 @@
                         }
                     }
 
-                globalSettings.registerContentObserver(RETAIL_MODE_SETTING, observer)
+                globalSettings.registerContentObserverSync(RETAIL_MODE_SETTING, observer)
 
-                awaitClose { globalSettings.unregisterContentObserver(observer) }
+                awaitClose { globalSettings.unregisterContentObserverSync(observer) }
             }
             .onStart { emit(Unit) }
             .map { globalSettings.getInt(RETAIL_MODE_SETTING, 0) != 0 }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 37f2a21..49810762 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -137,14 +137,14 @@
         public void startObserving() {
             if (!mObserving) {
                 mObserving = true;
-                mSecureSettings.registerContentObserverForUser(
+                mSecureSettings.registerContentObserverForUserSync(
                         BRIGHTNESS_MODE_URI,
                         false, this, UserHandle.USER_ALL);
             }
         }
 
         public void stopObserving() {
-            mSecureSettings.unregisterContentObserver(this);
+            mSecureSettings.unregisterContentObserverSync(this);
             mObserving = false;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
index 95768e5..2dfc920 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
@@ -318,10 +318,10 @@
             };
 
             // Register to listen for changes in Settings.Secure settings.
-            mSecureSettings.registerContentObserverForUser(
+            mSecureSettings.registerContentObserverForUserSync(
                     Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS, mContentObserver,
                     UserHandle.USER_CURRENT);
-            mSecureSettings.registerContentObserverForUser(
+            mSecureSettings.registerContentObserverForUserSync(
                     Settings.Secure.USER_SETUP_COMPLETE, mContentObserver,
                     UserHandle.USER_CURRENT);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 455c964..2e87a5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -143,6 +143,12 @@
     private var managedUserHandle: UserHandle? = null
     private var mSplitShadeEnabled = false
 
+    var suppressDisconnects = false
+        set(value) {
+            field = value
+            disconnect()
+        }
+
     // TODO(b/202758428): refactor so that we can test color updates via region samping, similar to
     //  how we test color updates when theme changes (See testThemeChangeUpdatesTextColor).
 
@@ -522,6 +528,7 @@
      */
     fun disconnect() {
         if (!smartspaceViews.isEmpty()) return
+        if (suppressDisconnects) return
 
         execution.assertIsMainThread()
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
index 4c2ef83..4c82bc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
@@ -37,11 +37,13 @@
 import javax.inject.Inject
 
 /**
- * A class which provides an adjustment object to the preparation coordinator which is uses
- * to ensure that notifications are reinflated when ranking-derived information changes.
+ * A class which provides an adjustment object to the preparation coordinator which is uses to
+ * ensure that notifications are reinflated when ranking-derived information changes.
  */
 @SysUISingleton
-class NotifUiAdjustmentProvider @Inject constructor(
+class NotifUiAdjustmentProvider
+@Inject
+constructor(
     @Main private val handler: Handler,
     private val secureSettings: SecureSettings,
     private val lockscreenUserManager: NotificationLockscreenUserManager,
@@ -53,14 +55,13 @@
     private val dirtyListeners = ListenerSet<Runnable>()
     private var isSnoozeSettingsEnabled = false
 
-    /**
-     *  Update the snooze enabled value on user switch
-     */
-    private val userTrackerCallback = object : UserTracker.Callback {
-        override fun onUserChanged(newUser: Int, userContext: Context) {
-            updateSnoozeEnabled()
+    /** Update the snooze enabled value on user switch */
+    private val userTrackerCallback =
+        object : UserTracker.Callback {
+            override fun onUserChanged(newUser: Int, userContext: Context) {
+                updateSnoozeEnabled()
+            }
         }
-    }
 
     init {
         userTracker.addCallback(userTrackerCallback, HandlerExecutor(handler))
@@ -75,7 +76,7 @@
                 )
             }
             updateSnoozeEnabled()
-            secureSettings.registerContentObserverForUser(
+            secureSettings.registerContentObserverForUserSync(
                 SHOW_NOTIFICATION_SNOOZE,
                 settingsObserver,
                 UserHandle.USER_ALL
@@ -93,7 +94,7 @@
                     onSensitiveStateChangedListener
                 )
             }
-            secureSettings.unregisterContentObserver(settingsObserver)
+            secureSettings.unregisterContentObserverSync(settingsObserver)
         }
     }
 
@@ -104,12 +105,13 @@
 
     private val onSensitiveStateChangedListener = Runnable { dirtyListeners.forEach(Runnable::run) }
 
-    private val settingsObserver = object : ContentObserver(handler) {
-        override fun onChange(selfChange: Boolean) {
-            updateSnoozeEnabled()
-            dirtyListeners.forEach(Runnable::run)
+    private val settingsObserver =
+        object : ContentObserver(handler) {
+            override fun onChange(selfChange: Boolean) {
+                updateSnoozeEnabled()
+                dirtyListeners.forEach(Runnable::run)
+            }
         }
-    }
 
     private fun updateSnoozeEnabled() {
         isSnoozeSettingsEnabled =
@@ -126,22 +128,23 @@
     }
 
     /**
-     * Returns a adjustment object for the given entry.  This can be compared to a previous instance
+     * Returns a adjustment object for the given entry. This can be compared to a previous instance
      * from the same notification using [NotifUiAdjustment.needReinflate] to determine if it should
      * be reinflated.
      */
-    fun calculateAdjustment(entry: NotificationEntry) = NotifUiAdjustment(
-        key = entry.key,
-        smartActions = entry.ranking.smartActions,
-        smartReplies = entry.ranking.smartReplies,
-        isConversation = entry.ranking.isConversation,
-        isSnoozeEnabled = isSnoozeSettingsEnabled && !entry.isCanceled,
-        isMinimized = isEntryMinimized(entry),
-        needsRedaction =
-            lockscreenUserManager.needsRedaction(entry) ||
-                (screenshareNotificationHiding() &&
-                    sensitiveNotifProtectionController.shouldProtectNotification(entry)),
-        isChildInGroup = entry.hasEverBeenGroupChild(),
-        isGroupSummary = entry.hasEverBeenGroupSummary(),
-    )
+    fun calculateAdjustment(entry: NotificationEntry) =
+        NotifUiAdjustment(
+            key = entry.key,
+            smartActions = entry.ranking.smartActions,
+            smartReplies = entry.ranking.smartReplies,
+            isConversation = entry.ranking.isConversation,
+            isSnoozeEnabled = isSnoozeSettingsEnabled && !entry.isCanceled,
+            isMinimized = isEntryMinimized(entry),
+            needsRedaction =
+                lockscreenUserManager.needsRedaction(entry) ||
+                    (screenshareNotificationHiding() &&
+                        sensitiveNotifProtectionController.shouldProtectNotification(entry)),
+            isChildInGroup = entry.hasEverBeenGroupChild(),
+            isGroupSummary = entry.hasEverBeenGroupSummary(),
+        )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 42a5bdf..f84b5f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -33,8 +33,8 @@
 import android.provider.Settings
 import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
 import android.provider.Settings.Global.HEADS_UP_OFF
+import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.UiEvent;
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.settings.UserTracker
@@ -86,7 +86,7 @@
                 }
             }
 
-        globalSettings.registerContentObserver(
+        globalSettings.registerContentObserverSync(
             globalSettings.getUriFor(HEADS_UP_NOTIFICATIONS_ENABLED),
             /* notifyForDescendants = */ true,
             observer
@@ -94,7 +94,7 @@
 
         // QQQ: Do we need to register for SETTING_HEADS_UP_TICKER? It seems unused.
 
-        observer.onChange(/* selfChange = */ true)
+        observer.onChange(/* selfChange= */ true)
     }
 }
 
@@ -243,6 +243,7 @@
     override fun shouldSuppress(entry: NotificationEntry) =
         keyguardNotificationVisibilityProvider.shouldHideNotification(entry)
 }
+
 class AvalancheSuppressor(
     private val avalancheProvider: AvalancheProvider,
     private val systemClock: SystemClock,
@@ -268,38 +269,33 @@
         SUPPRESS
     }
 
-     enum class AvalancheEvent(private val id: Int) : UiEventLogger.UiEventEnum {
-         @UiEvent(doc = "An avalanche event occurred but this notification was suppressed by a " +
-                 "non-avalanche suppressor.")
-         START(1802),
-
-         @UiEvent(doc = "HUN was suppressed in avalanche.")
-         SUPPRESS(1803),
-
-         @UiEvent(doc = "HUN allowed during avalanche because it is high priority.")
-         ALLOW_CONVERSATION_AFTER_AVALANCHE(1804),
-
-         @UiEvent(doc = "HUN allowed during avalanche because it is a high priority conversation.")
-         ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME(1805),
-
-         @UiEvent(doc = "HUN allowed during avalanche because it is a call.")
-         ALLOW_CALLSTYLE(1806),
-
-         @UiEvent(doc = "HUN allowed during avalanche because it is a calendar notification.")
-         ALLOW_CATEGORY_REMINDER(1807),
-
-         @UiEvent(doc = "HUN allowed during avalanche because it is a calendar notification.")
-         ALLOW_CATEGORY_EVENT(1808),
-
-         @UiEvent(doc = "HUN allowed during avalanche because it has a full screen intent and " +
-                 "the full screen intent permission is granted.")
-         ALLOW_FSI_WITH_PERMISSION_ON(1809),
-
-         @UiEvent(doc = "HUN allowed during avalanche because it is colorized.")
-         ALLOW_COLORIZED(1810),
-
-         @UiEvent(doc = "HUN allowed during avalanche because it is an emergency notification.")
-         ALLOW_EMERGENCY(1811);
+    enum class AvalancheEvent(private val id: Int) : UiEventLogger.UiEventEnum {
+        @UiEvent(
+            doc =
+                "An avalanche event occurred but this notification was suppressed by a " +
+                    "non-avalanche suppressor."
+        )
+        START(1802),
+        @UiEvent(doc = "HUN was suppressed in avalanche.") SUPPRESS(1803),
+        @UiEvent(doc = "HUN allowed during avalanche because it is high priority.")
+        ALLOW_CONVERSATION_AFTER_AVALANCHE(1804),
+        @UiEvent(doc = "HUN allowed during avalanche because it is a high priority conversation.")
+        ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME(1805),
+        @UiEvent(doc = "HUN allowed during avalanche because it is a call.") ALLOW_CALLSTYLE(1806),
+        @UiEvent(doc = "HUN allowed during avalanche because it is a calendar notification.")
+        ALLOW_CATEGORY_REMINDER(1807),
+        @UiEvent(doc = "HUN allowed during avalanche because it is a calendar notification.")
+        ALLOW_CATEGORY_EVENT(1808),
+        @UiEvent(
+            doc =
+                "HUN allowed during avalanche because it has a full screen intent and " +
+                    "the full screen intent permission is granted."
+        )
+        ALLOW_FSI_WITH_PERMISSION_ON(1809),
+        @UiEvent(doc = "HUN allowed during avalanche because it is colorized.")
+        ALLOW_COLORIZED(1810),
+        @UiEvent(doc = "HUN allowed during avalanche because it is an emergency notification.")
+        ALLOW_EMERGENCY(1811);
 
         override fun getId(): Int {
             return id
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index 332ece4..c7548aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -14,7 +14,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -41,8 +40,8 @@
 /** Determines if notifications should be visible based on the state of the keyguard. */
 interface KeyguardNotificationVisibilityProvider {
     /**
-     * Determines if the given notification should be hidden based on the current keyguard state.
-     * If a [Consumer] registered via [addOnStateChangedListener] is invoked, the results of this
+     * Determines if the given notification should be hidden based on the current keyguard state. If
+     * a [Consumer] registered via [addOnStateChangedListener] is invoked, the results of this
      * method may no longer be valid and should be re-queried.
      */
     fun shouldHideNotification(entry: NotificationEntry): Boolean
@@ -61,8 +60,9 @@
 @Module
 interface KeyguardNotificationVisibilityProviderImplModule {
     @Binds
-    fun bindImpl(impl: KeyguardNotificationVisibilityProviderImpl):
-            KeyguardNotificationVisibilityProvider
+    fun bindImpl(
+        impl: KeyguardNotificationVisibilityProviderImpl
+    ): KeyguardNotificationVisibilityProvider
 
     @Binds
     @IntoMap
@@ -71,7 +71,9 @@
 }
 
 @SysUISingleton
-class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
+class KeyguardNotificationVisibilityProviderImpl
+@Inject
+constructor(
     @Main private val handler: Handler,
     private val keyguardStateController: KeyguardStateController,
     private val lockscreenUserManager: NotificationLockscreenUserManager,
@@ -84,76 +86,88 @@
     private val featureFlags: FeatureFlagsClassic
 ) : CoreStartable, KeyguardNotificationVisibilityProvider {
     private val showSilentNotifsUri =
-            secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)
+        secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)
     private val onStateChangedListeners = ListenerSet<Consumer<String>>()
     private var hideSilentNotificationsOnLockscreen: Boolean = false
 
-    private val userTrackerCallback = object : UserTracker.Callback {
-        override fun onUserChanged(newUser: Int, userContext: Context) {
-            readShowSilentNotificationSetting()
-            if (isLockedOrLocking) {
-                // maybe public mode changed
-                notifyStateChanged("onUserSwitched")
+    private val userTrackerCallback =
+        object : UserTracker.Callback {
+            override fun onUserChanged(newUser: Int, userContext: Context) {
+                readShowSilentNotificationSetting()
+                if (isLockedOrLocking) {
+                    // maybe public mode changed
+                    notifyStateChanged("onUserSwitched")
+                }
             }
         }
-    }
 
     override fun start() {
         readShowSilentNotificationSetting()
-        keyguardStateController.addCallback(object : KeyguardStateController.Callback {
-            override fun onUnlockedChanged() {
-                notifyStateChanged("onUnlockedChanged")
-            }
+        keyguardStateController.addCallback(
+            object : KeyguardStateController.Callback {
+                override fun onUnlockedChanged() {
+                    notifyStateChanged("onUnlockedChanged")
+                }
 
-            override fun onKeyguardShowingChanged() {
-                notifyStateChanged("onKeyguardShowingChanged")
+                override fun onKeyguardShowingChanged() {
+                    notifyStateChanged("onKeyguardShowingChanged")
+                }
             }
-        })
-        keyguardUpdateMonitor.registerCallback(object : KeyguardUpdateMonitorCallback() {
-            override fun onStrongAuthStateChanged(userId: Int) {
-                notifyStateChanged("onStrongAuthStateChanged")
+        )
+        keyguardUpdateMonitor.registerCallback(
+            object : KeyguardUpdateMonitorCallback() {
+                override fun onStrongAuthStateChanged(userId: Int) {
+                    notifyStateChanged("onStrongAuthStateChanged")
+                }
             }
-        })
+        )
 
         // register lockscreen settings changed callbacks:
-        val settingsObserver: ContentObserver = object : ContentObserver(handler) {
-            override fun onChange(selfChange: Boolean, uri: Uri?) {
-                if (uri == showSilentNotifsUri) {
-                    readShowSilentNotificationSetting()
-                }
-                if (isLockedOrLocking) {
-                    notifyStateChanged("Settings $uri changed")
+        val settingsObserver: ContentObserver =
+            object : ContentObserver(handler) {
+                override fun onChange(selfChange: Boolean, uri: Uri?) {
+                    if (uri == showSilentNotifsUri) {
+                        readShowSilentNotificationSetting()
+                    }
+                    if (isLockedOrLocking) {
+                        notifyStateChanged("Settings $uri changed")
+                    }
                 }
             }
-        }
 
-        secureSettings.registerContentObserverForUser(
-                Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
-                settingsObserver,
-                UserHandle.USER_ALL)
+        secureSettings.registerContentObserverForUserSync(
+            Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+            settingsObserver,
+            UserHandle.USER_ALL
+        )
 
-        secureSettings.registerContentObserverForUser(
-                Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
-                true,
-                settingsObserver,
-                UserHandle.USER_ALL)
+        secureSettings.registerContentObserverForUserSync(
+            Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+            true,
+            settingsObserver,
+            UserHandle.USER_ALL
+        )
 
-        globalSettings.registerContentObserver(Settings.Global.ZEN_MODE, settingsObserver)
+        globalSettings.registerContentObserverSync(Settings.Global.ZEN_MODE, settingsObserver)
 
-        secureSettings.registerContentObserverForUser(
-                Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
-                settingsObserver,
-                UserHandle.USER_ALL)
+        secureSettings.registerContentObserverForUserSync(
+            Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
+            settingsObserver,
+            UserHandle.USER_ALL
+        )
 
         // register (maybe) public mode changed callbacks:
-        statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
-            override fun onStateChanged(newState: Int) {
-                notifyStateChanged("onStatusBarStateChanged")
+        statusBarStateController.addCallback(
+            object : StatusBarStateController.StateListener {
+                override fun onStateChanged(newState: Int) {
+                    notifyStateChanged("onStatusBarStateChanged")
+                }
+
+                override fun onUpcomingStateChanged(state: Int) {
+                    notifyStateChanged("onStatusBarUpcomingStateChanged")
+                }
             }
-            override fun onUpcomingStateChanged(state: Int) {
-                notifyStateChanged("onStatusBarUpcomingStateChanged")
-            }
-        })
+        )
         userTracker.addCallback(userTrackerCallback, HandlerExecutor(handler))
     }
 
@@ -169,45 +183,48 @@
         onStateChangedListeners.forEach { it.accept(reason) }
     }
 
-    override fun shouldHideNotification(entry: NotificationEntry): Boolean = when {
-        // Keyguard state doesn't matter if the keyguard is not showing.
-        !isLockedOrLocking -> false
-        // Notifications not allowed on the lockscreen, always hide.
-        !lockscreenUserManager.shouldShowLockscreenNotifications() -> true
-        // User settings do not allow this notification on the lockscreen, so hide it.
-        userSettingsDisallowNotification(entry) -> true
-        // Entry is explicitly marked SECRET, so hide it.
-        entry.sbn.notification.visibility == VISIBILITY_SECRET -> true
-        // if entry is silent, apply custom logic to see if should hide
-        shouldHideIfEntrySilent(entry) -> true
-        else -> false
-    }
+    override fun shouldHideNotification(entry: NotificationEntry): Boolean =
+        when {
+            // Keyguard state doesn't matter if the keyguard is not showing.
+            !isLockedOrLocking -> false
+            // Notifications not allowed on the lockscreen, always hide.
+            !lockscreenUserManager.shouldShowLockscreenNotifications() -> true
+            // User settings do not allow this notification on the lockscreen, so hide it.
+            userSettingsDisallowNotification(entry) -> true
+            // Entry is explicitly marked SECRET, so hide it.
+            entry.sbn.notification.visibility == VISIBILITY_SECRET -> true
+            // if entry is silent, apply custom logic to see if should hide
+            shouldHideIfEntrySilent(entry) -> true
+            else -> false
+        }
 
-    private fun shouldHideIfEntrySilent(entry: ListEntry): Boolean = when {
-        // Show if explicitly high priority (not hidden)
-        highPriorityProvider.isExplicitlyHighPriority(entry) -> false
-        // Ambient notifications are hidden always from lock screen
-        entry.representativeEntry?.isAmbient == true -> true
-        // [Now notification is silent]
-        // Hide regardless of parent priority if user wants silent notifs hidden
-        hideSilentNotificationsOnLockscreen -> true
-        // Parent priority is high enough to be shown on the lockscreen, do not hide.
-        entry.parent?.let(::shouldHideIfEntrySilent) == false -> false
-        // Show when silent notifications are allowed on lockscreen
-        else -> false
-    }
+    private fun shouldHideIfEntrySilent(entry: ListEntry): Boolean =
+        when {
+            // Show if explicitly high priority (not hidden)
+            highPriorityProvider.isExplicitlyHighPriority(entry) -> false
+            // Ambient notifications are hidden always from lock screen
+            entry.representativeEntry?.isAmbient == true -> true
+            // [Now notification is silent]
+            // Hide regardless of parent priority if user wants silent notifs hidden
+            hideSilentNotificationsOnLockscreen -> true
+            // Parent priority is high enough to be shown on the lockscreen, do not hide.
+            entry.parent?.let(::shouldHideIfEntrySilent) == false -> false
+            // Show when silent notifications are allowed on lockscreen
+            else -> false
+        }
 
     private fun userSettingsDisallowNotification(entry: NotificationEntry): Boolean {
-        fun disallowForUser(user: Int) = when {
-            // user is in lockdown, always disallow
-            keyguardUpdateMonitor.isUserInLockdown(user) -> true
-            // device isn't public, no need to check public-related settings, so allow
-            !lockscreenUserManager.isLockscreenPublicMode(user) -> false
-            // entry is meant to be secret on the lockscreen, disallow
-            isRankingVisibilitySecret(entry) -> true
-            // disallow if user disallows notifications in public
-            else -> !lockscreenUserManager.userAllowsNotificationsInPublic(user)
-        }
+        fun disallowForUser(user: Int) =
+            when {
+                // user is in lockdown, always disallow
+                keyguardUpdateMonitor.isUserInLockdown(user) -> true
+                // device isn't public, no need to check public-related settings, so allow
+                !lockscreenUserManager.isLockscreenPublicMode(user) -> false
+                // entry is meant to be secret on the lockscreen, disallow
+                isRankingVisibilitySecret(entry) -> true
+                // disallow if user disallows notifications in public
+                else -> !lockscreenUserManager.userAllowsNotificationsInPublic(user)
+            }
         val currentUser = lockscreenUserManager.currentUserId
         val notifUser = entry.sbn.user.identifier
         return when {
@@ -222,28 +239,35 @@
         // ranking.lockscreenVisibilityOverride contains possibly out of date DPC and Setting
         // info, and NotificationLockscreenUserManagerImpl is already listening for updates
         // to those
-        return entry.ranking.channel != null && entry.ranking.channel.lockscreenVisibility ==
-                    VISIBILITY_SECRET
+        return entry.ranking.channel != null &&
+            entry.ranking.channel.lockscreenVisibility == VISIBILITY_SECRET
     }
 
-    override fun dump(pw: PrintWriter, args: Array<out String>) = pw.asIndenting().run {
-        println("isLockedOrLocking", isLockedOrLocking)
-        withIncreasedIndent {
-            println("keyguardStateController.isShowing", keyguardStateController.isShowing)
-            println("statusBarStateController.currentOrUpcomingState",
-                statusBarStateController.currentOrUpcomingState)
+    override fun dump(pw: PrintWriter, args: Array<out String>) =
+        pw.asIndenting().run {
+            println("isLockedOrLocking", isLockedOrLocking)
+            withIncreasedIndent {
+                println("keyguardStateController.isShowing", keyguardStateController.isShowing)
+                println(
+                    "statusBarStateController.currentOrUpcomingState",
+                    statusBarStateController.currentOrUpcomingState
+                )
+            }
+            println("hideSilentNotificationsOnLockscreen", hideSilentNotificationsOnLockscreen)
         }
-        println("hideSilentNotificationsOnLockscreen", hideSilentNotificationsOnLockscreen)
-    }
 
-    private val isLockedOrLocking get() =
-        keyguardStateController.isShowing ||
+    private val isLockedOrLocking
+        get() =
+            keyguardStateController.isShowing ||
                 statusBarStateController.currentOrUpcomingState == StatusBarState.KEYGUARD
 
     private fun readShowSilentNotificationSetting() {
         val showSilentNotifs =
-                secureSettings.getBoolForUser(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
-                        false, UserHandle.USER_CURRENT)
+            secureSettings.getBoolForUser(
+                Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
+                false,
+                UserHandle.USER_CURRENT
+            )
         hideSilentNotificationsOnLockscreen = !showSilentNotifs
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 74925c8..fea360d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -171,11 +171,11 @@
         };
 
         if (ENABLE_HEADS_UP) {
-            mGlobalSettings.registerContentObserver(
+            mGlobalSettings.registerContentObserverSync(
                     mGlobalSettings.getUriFor(HEADS_UP_NOTIFICATIONS_ENABLED),
                     true,
                     headsUpObserver);
-            mGlobalSettings.registerContentObserver(
+            mGlobalSettings.registerContentObserverSync(
                     mGlobalSettings.getUriFor(SETTING_HEADS_UP_TICKER), true,
                     headsUpObserver);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java
index a17c066..30dbfed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java
@@ -92,9 +92,9 @@
 
                 synchronized (mListeners) {
                     if (mListeners.size() > 0) {
-                        mSecureSettings.unregisterContentObserver(mContentObserver);
+                        mSecureSettings.unregisterContentObserverSync(mContentObserver);
                         for (Uri uri : mListeners.keySet()) {
-                            mSecureSettings.registerContentObserverForUser(
+                            mSecureSettings.registerContentObserverForUserSync(
                                     uri, false, mContentObserver, newUser);
                         }
                     }
@@ -131,7 +131,7 @@
             mListeners.put(uri, currentListeners);
             if (currentListeners.size() == 1) {
                 mBackgroundHandler.post(() -> {
-                    mSecureSettings.registerContentObserverForUser(
+                    mSecureSettings.registerContentObserverForUserSync(
                             uri, false, mContentObserver, mUserTracker.getUserId());
                 });
             }
@@ -159,7 +159,7 @@
 
             if (mListeners.size() == 0) {
                 mBackgroundHandler.post(() -> {
-                    mSecureSettings.unregisterContentObserver(mContentObserver);
+                    mSecureSettings.unregisterContentObserverSync(mContentObserver);
                 });
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 6d4301f..e44edcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -398,7 +398,7 @@
         mSystemIconsContainer.setOnHoverListener(hoverListener);
         mView.setOnApplyWindowInsetsListener(
                 (view, windowInsets) -> mView.updateWindowInsets(windowInsets, mInsetsProvider));
-        mSecureSettings.registerContentObserverForUser(
+        mSecureSettings.registerContentObserverForUserSync(
                 Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
                 false,
                 mVolumeSettingObserver,
@@ -416,7 +416,7 @@
         mStatusBarStateController.removeCallback(mStatusBarStateListener);
         mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
         mDisableStateTracker.stopTracking(mCommandQueue);
-        mSecureSettings.unregisterContentObserver(mVolumeSettingObserver);
+        mSecureSettings.unregisterContentObserverSync(mVolumeSettingObserver);
         if (mTintedIconManager != null) {
             mStatusBarIconController.removeIconGroup(mTintedIconManager);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 479aef1..c53558e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -23,9 +23,9 @@
 import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -162,7 +162,7 @@
         this.centralSurfaces = centralSurfaces
 
         updateAnimatorDurationScale()
-        globalSettings.registerContentObserver(
+        globalSettings.registerContentObserverSync(
             Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
             /* notify for descendants */ false,
             animatorDurationScaleObserver
@@ -376,8 +376,9 @@
         // We currently draw both the light reveal scrim, and the AOD UI, in the shade. If it's
         // already expanded and showing notifications/QS, the animation looks really messy. For now,
         // disable it if the notification panel is expanded.
-        if ((!this::centralSurfaces.isInitialized ||
-                    panelExpansionInteractorLazy.get().isPanelExpanded) &&
+        if (
+            (!this::centralSurfaces.isInitialized ||
+                panelExpansionInteractorLazy.get().isPanelExpanded) &&
                 // Status bar might be expanded because we have started
                 // playing the animation already
                 !isAnimationPlaying()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index aac211a..3d8090d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -80,6 +80,10 @@
 import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
 import com.android.systemui.util.settings.SecureSettings;
 
+import kotlin.Unit;
+
+import kotlinx.coroutines.DisposableHandle;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -90,10 +94,6 @@
 
 import javax.inject.Inject;
 
-import kotlin.Unit;
-
-import kotlinx.coroutines.DisposableHandle;
-
 /**
  * Contains the collapsed status bar and handles hiding/showing based on disable flags
  * and keyguard state. Also manages lifecycle to make sure the views it contains are being
@@ -431,7 +431,7 @@
         initOngoingCallChip();
         mAnimationScheduler.addCallback(this);
 
-        mSecureSettings.registerContentObserverForUser(
+        mSecureSettings.registerContentObserverForUserSync(
                 Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
                 false,
                 mVolumeSettingObserver,
@@ -445,7 +445,7 @@
         mStatusBarStateController.removeCallback(this);
         mOngoingCallController.removeCallback(mOngoingCallListener);
         mAnimationScheduler.removeCallback(this);
-        mSecureSettings.unregisterContentObserver(mVolumeSettingObserver);
+        mSecureSettings.unregisterContentObserverSync(mVolumeSettingObserver);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 4bd8681..fad5df8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -138,7 +138,7 @@
                 }
             }
         };
-        globalSettings.registerContentObserver(
+        globalSettings.registerContentObserverSync(
                 globalSettings.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS),
                 /* notifyForDescendants = */ false,
                 settingsObserver);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
index 8b63dfe..8123f8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
@@ -42,16 +42,16 @@
 import javax.inject.Inject
 
 @SysUISingleton
-open class DeviceProvisionedControllerImpl @Inject constructor(
+open class DeviceProvisionedControllerImpl
+@Inject
+constructor(
     private val secureSettings: SecureSettings,
     private val globalSettings: GlobalSettings,
     private val userTracker: UserTracker,
     private val dumpManager: DumpManager,
     @Background private val backgroundHandler: Handler,
     @Main private val mainExecutor: Executor
-) : DeviceProvisionedController,
-    DeviceProvisionedController.DeviceProvisionedListener,
-    Dumpable {
+) : DeviceProvisionedController, DeviceProvisionedController.DeviceProvisionedListener, Dumpable {
 
     companion object {
         private const val ALL_USERS = -1
@@ -63,8 +63,7 @@
     private val userSetupUri = secureSettings.getUriFor(Settings.Secure.USER_SETUP_COMPLETE)
 
     private val deviceProvisioned = AtomicBoolean(false)
-    @GuardedBy("lock")
-    private val userSetupComplete = SparseBooleanArray()
+    @GuardedBy("lock") private val userSetupComplete = SparseBooleanArray()
     @GuardedBy("lock")
     private val listeners = ArraySet<DeviceProvisionedController.DeviceProvisionedListener>()
 
@@ -81,42 +80,42 @@
         return _currentUser
     }
 
-    private val observer = object : ContentObserver(backgroundHandler) {
-        override fun onChange(
-            selfChange: Boolean,
-            uris: MutableCollection<Uri>,
-            flags: Int,
-            userId: Int
-        ) {
-            val updateDeviceProvisioned = deviceProvisionedUri in uris
-            val updateUser = if (userSetupUri in uris) userId else NO_USERS
-            updateValues(updateDeviceProvisioned, updateUser)
-            if (updateDeviceProvisioned) {
-                onDeviceProvisionedChanged()
-            }
-            if (updateUser != NO_USERS) {
-                onUserSetupChanged()
+    private val observer =
+        object : ContentObserver(backgroundHandler) {
+            override fun onChange(
+                selfChange: Boolean,
+                uris: MutableCollection<Uri>,
+                flags: Int,
+                userId: Int
+            ) {
+                val updateDeviceProvisioned = deviceProvisionedUri in uris
+                val updateUser = if (userSetupUri in uris) userId else NO_USERS
+                updateValues(updateDeviceProvisioned, updateUser)
+                if (updateDeviceProvisioned) {
+                    onDeviceProvisionedChanged()
+                }
+                if (updateUser != NO_USERS) {
+                    onUserSetupChanged()
+                }
             }
         }
-    }
 
-    private val userChangedCallback = object : UserTracker.Callback {
-        @WorkerThread
-        override fun onUserChanged(newUser: Int, userContext: Context) {
-            updateValues(updateDeviceProvisioned = false, updateUser = newUser)
-            onUserSwitched()
+    private val userChangedCallback =
+        object : UserTracker.Callback {
+            @WorkerThread
+            override fun onUserChanged(newUser: Int, userContext: Context) {
+                updateValues(updateDeviceProvisioned = false, updateUser = newUser)
+                onUserSwitched()
+            }
+
+            override fun onProfilesChanged(profiles: List<UserInfo>) {}
         }
 
-        override fun onProfilesChanged(profiles: List<UserInfo>) {}
-    }
-
     init {
         userSetupComplete.put(currentUser, false)
     }
 
-    /**
-     * Call to initialize values and register observers
-     */
+    /** Call to initialize values and register observers */
     open fun init() {
         if (!initted.compareAndSet(false, true)) {
             return
@@ -124,31 +123,39 @@
         dumpManager.registerDumpable(this)
         updateValues()
         userTracker.addCallback(userChangedCallback, backgroundExecutor)
-        globalSettings.registerContentObserver(deviceProvisionedUri, observer)
-        secureSettings.registerContentObserverForUser(userSetupUri, observer, UserHandle.USER_ALL)
+        globalSettings.registerContentObserverSync(deviceProvisionedUri, observer)
+        secureSettings.registerContentObserverForUserSync(
+            userSetupUri,
+            observer,
+            UserHandle.USER_ALL
+        )
     }
 
     @WorkerThread
-    private fun updateValues(
-            updateDeviceProvisioned: Boolean = true,
-            updateUser: Int = ALL_USERS
-    ) {
+    private fun updateValues(updateDeviceProvisioned: Boolean = true, updateUser: Int = ALL_USERS) {
         if (updateDeviceProvisioned) {
-            deviceProvisioned
-                    .set(globalSettings.getInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0)
+            deviceProvisioned.set(globalSettings.getInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0)
         }
         synchronized(lock) {
             if (updateUser == ALL_USERS) {
                 val n = userSetupComplete.size()
                 for (i in 0 until n) {
                     val user = userSetupComplete.keyAt(i)
-                    val value = secureSettings
-                            .getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, user) != 0
+                    val value =
+                        secureSettings.getIntForUser(
+                            Settings.Secure.USER_SETUP_COMPLETE,
+                            0,
+                            user
+                        ) != 0
                     userSetupComplete.put(user, value)
                 }
             } else if (updateUser != NO_USERS) {
-                val value = secureSettings
-                        .getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, updateUser) != 0
+                val value =
+                    secureSettings.getIntForUser(
+                        Settings.Secure.USER_SETUP_COMPLETE,
+                        0,
+                        updateUser
+                    ) != 0
                 userSetupComplete.put(updateUser, value)
             }
         }
@@ -160,15 +167,11 @@
      * The listener will not be called when this happens.
      */
     override fun addCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) {
-        synchronized(lock) {
-            listeners.add(listener)
-        }
+        synchronized(lock) { listeners.add(listener) }
     }
 
     override fun removeCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) {
-        synchronized(lock) {
-            listeners.remove(listener)
-        }
+        synchronized(lock) { listeners.remove(listener) }
     }
 
     override fun isDeviceProvisioned(): Boolean {
@@ -176,20 +179,14 @@
     }
 
     override fun isUserSetup(user: Int): Boolean {
-        val index = synchronized(lock) {
-            userSetupComplete.indexOfKey(user)
-        }
+        val index = synchronized(lock) { userSetupComplete.indexOfKey(user) }
         return if (index < 0) {
-            val value = secureSettings
-                    .getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, user) != 0
-            synchronized(lock) {
-                userSetupComplete.put(user, value)
-            }
+            val value =
+                secureSettings.getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, user) != 0
+            synchronized(lock) { userSetupComplete.put(user, value) }
             value
         } else {
-            synchronized(lock) {
-                userSetupComplete.get(user, false)
-            }
+            synchronized(lock) { userSetupComplete.get(user, false) }
         }
     }
 
@@ -214,12 +211,8 @@
     protected fun dispatchChange(
         callback: DeviceProvisionedController.DeviceProvisionedListener.() -> Unit
     ) {
-        val listenersCopy = synchronized(lock) {
-            ArrayList(listeners)
-        }
-        mainExecutor.execute {
-            listenersCopy.forEach(callback)
-        }
+        val listenersCopy = synchronized(lock) { ArrayList(listeners) }
+        mainExecutor.execute { listenersCopy.forEach(callback) }
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
@@ -229,4 +222,4 @@
             pw.println("Listeners: $listeners")
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 9eee5d0..f57b696 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -115,7 +115,7 @@
         };
 
         // Register to listen for changes in Settings.Secure settings.
-        mSecureSettings.registerContentObserverForUser(
+        mSecureSettings.registerContentObserverForUserSync(
                 Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, mContentObserver, UserHandle.USER_ALL);
 
         // Register to listen for changes in DeviceConfig settings.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
index 40bb67f..9ab8175 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -188,7 +188,7 @@
                 });
             }
         };
-        settings.registerContentObserver(
+        settings.registerContentObserverSync(
                 DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS,
                 developerOptionsObserver);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 600005b..e09e577 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -140,13 +140,13 @@
         mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
         if (registerZenModeContentObserverBackground()) {
             bgHandler.post(() -> {
-                globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver);
-                globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG,
+                globalSettings.registerContentObserverSync(Global.ZEN_MODE, modeContentObserver);
+                globalSettings.registerContentObserverSync(Global.ZEN_MODE_CONFIG_ETAG,
                         configContentObserver);
             });
         } else {
-            globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver);
-            globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG,
+            globalSettings.registerContentObserverSync(Global.ZEN_MODE, modeContentObserver);
+            globalSettings.registerContentObserverSync(Global.ZEN_MODE_CONFIG_ETAG,
                     configContentObserver);
         }
         updateZenMode(getModeSettingValueFromProvider());
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 7494649..4963aae 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -444,7 +444,7 @@
         filter.addAction(Intent.ACTION_WALLPAPER_CHANGED);
         mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, mMainExecutor,
                 UserHandle.ALL);
-        mSecureSettings.registerContentObserverForUser(
+        mSecureSettings.registerContentObserverForUserSync(
                 Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
                 false,
                 new ContentObserver(mBgHandler) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
index ec89610..ed52233 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
@@ -37,7 +37,6 @@
 interface SettingsProxy {
     /** Returns the [ContentResolver] this instance was constructed with. */
     fun getContentResolver(): ContentResolver
-
     /**
      * Construct the content URI for a particular name/value pair, useful for monitoring changes
      * with a ContentObserver.
@@ -46,42 +45,36 @@
      * @return the corresponding content URI, or null if not present
      */
     fun getUriFor(name: String): Uri
-
     /**
      * Convenience wrapper around [ContentResolver.registerContentObserver].'
      *
      * Implicitly calls [getUriFor] on the passed in name.
      */
-    fun registerContentObserver(name: String, settingsObserver: ContentObserver) {
-        registerContentObserver(getUriFor(name), settingsObserver)
+    fun registerContentObserverSync(name: String, settingsObserver: ContentObserver) {
+        registerContentObserverSync(getUriFor(name), settingsObserver)
     }
-
     /** Convenience wrapper around [ContentResolver.registerContentObserver].' */
-    fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) =
-        registerContentObserver(uri, false, settingsObserver)
-
+    fun registerContentObserverSync(uri: Uri, settingsObserver: ContentObserver) =
+        registerContentObserverSync(uri, false, settingsObserver)
     /**
      * Convenience wrapper around [ContentResolver.registerContentObserver].'
      *
      * Implicitly calls [getUriFor] on the passed in name.
      */
-    fun registerContentObserver(
+    fun registerContentObserverSync(
         name: String,
         notifyForDescendants: Boolean,
         settingsObserver: ContentObserver
-    ) = registerContentObserver(getUriFor(name), notifyForDescendants, settingsObserver)
-
+    ) = registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
     /** Convenience wrapper around [ContentResolver.registerContentObserver].' */
-    fun registerContentObserver(
+    fun registerContentObserverSync(
         uri: Uri,
         notifyForDescendants: Boolean,
         settingsObserver: ContentObserver
     ) = getContentResolver().registerContentObserver(uri, notifyForDescendants, settingsObserver)
-
     /** See [ContentResolver.unregisterContentObserver]. */
-    fun unregisterContentObserver(settingsObserver: ContentObserver) =
+    fun unregisterContentObserverSync(settingsObserver: ContentObserver) =
         getContentResolver().unregisterContentObserver(settingsObserver)
-
     /**
      * Look up a name in the database.
      *
@@ -89,7 +82,6 @@
      * @return the corresponding value, or null if not present
      */
     fun getString(name: String): String
-
     /**
      * Store a name/value pair into the database.
      *
@@ -98,7 +90,6 @@
      * @return true if the value was set, false on database errors
      */
     fun putString(name: String, value: String): Boolean
-
     /**
      * Store a name/value pair into the database.
      *
@@ -129,7 +120,6 @@
      * @see .resetToDefaults
      */
     fun putString(name: String, value: String, tag: String, makeDefault: Boolean): Boolean
-
     /**
      * Convenience function for retrieving a single secure settings value as an integer. Note that
      * internally setting values are always stored as strings; this function converts the string to
@@ -148,7 +138,6 @@
             def
         }
     }
-
     /**
      * Convenience function for retrieving a single secure settings value as an integer. Note that
      * internally setting values are always stored as strings; this function converts the string to
@@ -171,7 +160,6 @@
             throw SettingNotFoundException(name)
         }
     }
-
     /**
      * Convenience function for updating a single settings value as an integer. This will either
      * create a new entry in the table if the given name does not exist, or modify the value of the
@@ -185,7 +173,6 @@
     fun putInt(name: String, value: Int): Boolean {
         return putString(name, value.toString())
     }
-
     /**
      * Convenience function for retrieving a single secure settings value as a boolean. Note that
      * internally setting values are always stored as strings; this function converts the string to
@@ -199,7 +186,6 @@
     fun getBool(name: String, def: Boolean): Boolean {
         return getInt(name, if (def) 1 else 0) != 0
     }
-
     /**
      * Convenience function for retrieving a single secure settings value as a boolean. Note that
      * internally setting values are always stored as strings; this function converts the string to
@@ -217,7 +203,6 @@
     fun getBool(name: String): Boolean {
         return getInt(name) != 0
     }
-
     /**
      * Convenience function for updating a single settings value as a boolean. This will either
      * create a new entry in the table if the given name does not exist, or modify the value of the
@@ -231,7 +216,6 @@
     fun putBool(name: String, value: Boolean): Boolean {
         return putInt(name, if (value) 1 else 0)
     }
-
     /**
      * Convenience function for retrieving a single secure settings value as a `long`. Note that
      * internally setting values are always stored as strings; this function converts the string to
@@ -246,7 +230,6 @@
         val valString = getString(name)
         return parseLongOrUseDefault(valString, def)
     }
-
     /**
      * Convenience function for retrieving a single secure settings value as a `long`. Note that
      * internally setting values are always stored as strings; this function converts the string to
@@ -265,7 +248,6 @@
         val valString = getString(name)
         return parseLongOrThrow(name, valString)
     }
-
     /**
      * Convenience function for updating a secure settings value as a long integer. This will either
      * create a new entry in the table if the given name does not exist, or modify the value of the
@@ -279,7 +261,6 @@
     fun putLong(name: String, value: Long): Boolean {
         return putString(name, value.toString())
     }
-
     /**
      * Convenience function for retrieving a single secure settings value as a floating point
      * number. Note that internally setting values are always stored as strings; this function
@@ -294,7 +275,6 @@
         val v = getString(name)
         return parseFloat(v, def)
     }
-
     /**
      * Convenience function for retrieving a single secure settings value as a float. Note that
      * internally setting values are always stored as strings; this function converts the string to
@@ -313,7 +293,6 @@
         val v = getString(name)
         return parseFloatOrThrow(name, v)
     }
-
     /**
      * Convenience function for updating a single settings value as a floating point number. This
      * will either create a new entry in the table if the given name does not exist, or modify the
@@ -327,7 +306,6 @@
     fun putFloat(name: String, value: Float): Boolean {
         return putString(name, value.toString())
     }
-
     companion object {
         /** Convert a string to a long, or uses a default if the string is malformed or null */
         @JvmStatic
@@ -341,7 +319,6 @@
                 }
             return value
         }
-
         /** Convert a string to a long, or throws an exception if the string is malformed or null */
         @JvmStatic
         @Throws(SettingNotFoundException::class)
@@ -355,7 +332,6 @@
                 throw SettingNotFoundException(name)
             }
         }
-
         /** Convert a string to a float, or uses a default if the string is malformed or null */
         @JvmStatic
         fun parseFloat(v: String?, def: Float): Float {
@@ -365,7 +341,6 @@
                 def
             }
         }
-
         /**
          * Convert a string to a float, or throws an exception if the string is malformed or null
          */
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
index 7484368..d757e33 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
@@ -39,9 +39,9 @@
                     }
                 }
 
-            names.forEach { name -> registerContentObserverForUser(name, observer, userId) }
+            names.forEach { name -> registerContentObserverForUserSync(name, observer, userId) }
 
-            awaitClose { unregisterContentObserver(observer) }
+            awaitClose { unregisterContentObserverSync(observer) }
         }
     }
 
@@ -57,9 +57,9 @@
                     }
                 }
 
-            names.forEach { name -> registerContentObserver(name, observer) }
+            names.forEach { name -> registerContentObserverSync(name, observer) }
 
-            awaitClose { unregisterContentObserver(observer) }
+            awaitClose { unregisterContentObserverSync(observer) }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
index 2285270..ed13943 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -41,10 +41,8 @@
  * instances, unifying setting related actions in one place.
  */
 interface UserSettingsProxy : SettingsProxy {
-
     /** Returns that [UserTracker] this instance was constructed with. */
     val userTracker: UserTracker
-
     /** Returns the user id for the associated [ContentResolver]. */
     var userId: Int
         get() = getContentResolver().userId
@@ -53,7 +51,6 @@
                 "userId cannot be set in interface, use setter from an implementation instead."
             )
         }
-
     /**
      * Returns the actual current user handle when querying with the current user. Otherwise,
      * returns the passed in user id.
@@ -63,63 +60,57 @@
             userHandle
         } else userTracker.userId
     }
-
-    override fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) {
-        registerContentObserverForUser(uri, settingsObserver, userId)
+    override fun registerContentObserverSync(uri: Uri, settingsObserver: ContentObserver) {
+        registerContentObserverForUserSync(uri, settingsObserver, userId)
     }
-
     /** Convenience wrapper around [ContentResolver.registerContentObserver].' */
-    override fun registerContentObserver(
+    override fun registerContentObserverSync(
         uri: Uri,
         notifyForDescendants: Boolean,
         settingsObserver: ContentObserver
     ) {
-        registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, userId)
+        registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId)
     }
-
     /**
      * Convenience wrapper around [ContentResolver.registerContentObserver]
      *
      * Implicitly calls [getUriFor] on the passed in name.
      */
-    fun registerContentObserverForUser(
+    fun registerContentObserverForUserSync(
         name: String,
         settingsObserver: ContentObserver,
         userHandle: Int
     ) {
-        registerContentObserverForUser(getUriFor(name), settingsObserver, userHandle)
+        registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle)
     }
-
     /** Convenience wrapper around [ContentResolver.registerContentObserver] */
-    fun registerContentObserverForUser(
+    fun registerContentObserverForUserSync(
         uri: Uri,
         settingsObserver: ContentObserver,
         userHandle: Int
     ) {
-        registerContentObserverForUser(uri, false, settingsObserver, userHandle)
+        registerContentObserverForUserSync(uri, false, settingsObserver, userHandle)
     }
-
     /**
      * Convenience wrapper around [ContentResolver.registerContentObserver]
      *
      * Implicitly calls [getUriFor] on the passed in name.
      */
-    fun registerContentObserverForUser(
+    fun registerContentObserverForUserSync(
         name: String,
         notifyForDescendants: Boolean,
         settingsObserver: ContentObserver,
         userHandle: Int
     ) {
-        registerContentObserverForUser(
+        registerContentObserverForUserSync(
             getUriFor(name),
             notifyForDescendants,
             settingsObserver,
             userHandle
         )
     }
-
     /** Convenience wrapper around [ContentResolver.registerContentObserver] */
-    fun registerContentObserverForUser(
+    fun registerContentObserverForUserSync(
         uri: Uri,
         notifyForDescendants: Boolean,
         settingsObserver: ContentObserver,
@@ -136,7 +127,6 @@
             Unit
         }
     }
-
     /**
      * Look up a name in the database.
      *
@@ -146,10 +136,8 @@
     override fun getString(name: String): String {
         return getStringForUser(name, userId)
     }
-
     /** See [getString]. */
     fun getStringForUser(name: String, userHandle: Int): String
-
     /**
      * Store a name/value pair into the database. Values written by this method will be overridden
      * if a restore happens in the future.
@@ -162,10 +150,8 @@
     override fun putString(name: String, value: String): Boolean {
         return putStringForUser(name, value, userId)
     }
-
     /** Similar implementation to [putString] for the specified [userHandle]. */
     fun putStringForUser(name: String, value: String, userHandle: Int): Boolean
-
     /** Similar implementation to [putString] for the specified [userHandle]. */
     fun putStringForUser(
         name: String,
@@ -175,11 +161,9 @@
         @UserIdInt userHandle: Int,
         overrideableByRestore: Boolean
     ): Boolean
-
     override fun getInt(name: String, def: Int): Int {
         return getIntForUser(name, def, userId)
     }
-
     /** Similar implementation to [getInt] for the specified [userHandle]. */
     fun getIntForUser(name: String, def: Int, userHandle: Int): Int {
         val v = getStringForUser(name, userHandle)
@@ -189,10 +173,8 @@
             def
         }
     }
-
     @Throws(SettingNotFoundException::class)
     override fun getInt(name: String) = getIntForUser(name, userId)
-
     /** Similar implementation to [getInt] for the specified [userHandle]. */
     @Throws(SettingNotFoundException::class)
     fun getIntForUser(name: String, userHandle: Int): Int {
@@ -203,66 +185,52 @@
             throw SettingNotFoundException(name)
         }
     }
-
     override fun putInt(name: String, value: Int) = putIntForUser(name, value, userId)
-
     /** Similar implementation to [getInt] for the specified [userHandle]. */
     fun putIntForUser(name: String, value: Int, userHandle: Int) =
         putStringForUser(name, value.toString(), userHandle)
-
     override fun getBool(name: String, def: Boolean) = getBoolForUser(name, def, userId)
-
     /** Similar implementation to [getBool] for the specified [userHandle]. */
     fun getBoolForUser(name: String, def: Boolean, userHandle: Int) =
         getIntForUser(name, if (def) 1 else 0, userHandle) != 0
-
     @Throws(SettingNotFoundException::class)
     override fun getBool(name: String) = getBoolForUser(name, userId)
-
     /** Similar implementation to [getBool] for the specified [userHandle]. */
     @Throws(SettingNotFoundException::class)
     fun getBoolForUser(name: String, userHandle: Int): Boolean {
         return getIntForUser(name, userHandle) != 0
     }
-
     override fun putBool(name: String, value: Boolean): Boolean {
         return putBoolForUser(name, value, userId)
     }
-
     /** Similar implementation to [putBool] for the specified [userHandle]. */
     fun putBoolForUser(name: String, value: Boolean, userHandle: Int) =
         putIntForUser(name, if (value) 1 else 0, userHandle)
-
     /** Similar implementation to [getLong] for the specified [userHandle]. */
     fun getLongForUser(name: String, def: Long, userHandle: Int): Long {
         val valString = getStringForUser(name, userHandle)
         return parseLongOrUseDefault(valString, def)
     }
-
     /** Similar implementation to [getLong] for the specified [userHandle]. */
     @Throws(SettingNotFoundException::class)
     fun getLongForUser(name: String, userHandle: Int): Long {
         val valString = getStringForUser(name, userHandle)
         return parseLongOrThrow(name, valString)
     }
-
     /** Similar implementation to [putLong] for the specified [userHandle]. */
     fun putLongForUser(name: String, value: Long, userHandle: Int) =
         putStringForUser(name, value.toString(), userHandle)
-
     /** Similar implementation to [getFloat] for the specified [userHandle]. */
     fun getFloatForUser(name: String, def: Float, userHandle: Int): Float {
         val v = getStringForUser(name, userHandle)
         return parseFloat(v, def)
     }
-
     /** Similar implementation to [getFloat] for the specified [userHandle]. */
     @Throws(SettingNotFoundException::class)
     fun getFloatForUser(name: String, userHandle: Int): Float {
         val v = getStringForUser(name, userHandle)
         return parseFloatOrThrow(name, v)
     }
-
     /** Similar implementation to [putFloat] for the specified [userHandle]. */
     fun putFloatForUser(name: String, value: Float, userHandle: Int) =
         putStringForUser(name, value.toString(), userHandle)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
index e0f64b4..154737c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
@@ -17,15 +17,18 @@
 package com.android.systemui.volume.domain.interactor
 
 import android.bluetooth.BluetoothAdapter
+import android.content.Context
 import android.media.AudioDeviceInfo
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.settingslib.media.BluetoothMediaDevice
 import com.android.settingslib.media.MediaDevice
 import com.android.settingslib.media.MediaDevice.MediaDeviceType
+import com.android.settingslib.media.PhoneMediaDevice
 import com.android.settingslib.volume.data.repository.AudioRepository
 import com.android.settingslib.volume.data.repository.AudioSharingRepository
 import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.volume.domain.model.AudioOutputDevice
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
@@ -36,6 +39,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
@@ -47,6 +51,7 @@
 class AudioOutputInteractor
 @Inject
 constructor(
+    @Application private val context: Context,
     audioRepository: AudioRepository,
     audioModeInteractor: AudioModeInteractor,
     @VolumePanelScope scope: CoroutineScope,
@@ -58,7 +63,7 @@
     audioSharingRepository: AudioSharingRepository,
 ) {
 
-    val currentAudioDevice: Flow<AudioOutputDevice> =
+    val currentAudioDevice: StateFlow<AudioOutputDevice> =
         audioModeInteractor.isOngoingCall
             .flatMapLatest { isOngoingCall ->
                 if (isOngoingCall) {
@@ -102,7 +107,7 @@
             )
         }
         return AudioOutputDevice.BuiltIn(
-            name = productName.toString(),
+            name = PhoneMediaDevice.getMediaTransferThisDeviceName(context),
             icon = deviceIconInteractor.loadIcon(type),
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
index 199bc3b..333f4ad 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.animation.Expandable
 import com.android.systemui.media.dialog.MediaOutputDialogManager
-import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlaybackState
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaOutputComponentModel
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import javax.inject.Inject
 
@@ -29,14 +29,12 @@
 @VolumePanelScope
 class MediaOutputActionsInteractor
 @Inject
-constructor(
-    private val mediaOutputDialogManager: MediaOutputDialogManager,
-) {
+constructor(private val mediaOutputDialogManager: MediaOutputDialogManager) {
 
-    fun onBarClick(sessionWithPlaybackState: SessionWithPlaybackState?, expandable: Expandable?) {
-        if (sessionWithPlaybackState?.isPlaybackActive == true) {
+    fun onBarClick(model: MediaOutputComponentModel?, expandable: Expandable?) {
+        if (model is MediaOutputComponentModel.MediaSession) {
             mediaOutputDialogManager.createAndShowWithController(
-                sessionWithPlaybackState.session.packageName,
+                model.session.packageName,
                 false,
                 expandable?.dialogController()
             )
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
new file mode 100644
index 0000000..ed25129
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
+
+import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.systemui.volume.domain.interactor.AudioOutputInteractor
+import com.android.systemui.volume.domain.model.AudioOutputDevice
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaOutputComponentModel
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlaybackState
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.Result
+import com.android.systemui.volume.panel.shared.model.filterData
+import com.android.systemui.volume.panel.shared.model.wrapInResult
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
+
+/** Gathers together a domain state for the Media Output Volume Panel component. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@VolumePanelScope
+class MediaOutputComponentInteractor
+@Inject
+constructor(
+    @VolumePanelScope private val coroutineScope: CoroutineScope,
+    private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
+    audioOutputInteractor: AudioOutputInteractor,
+    audioModeInteractor: AudioModeInteractor,
+    interactor: MediaOutputInteractor,
+) {
+
+    private val sessionWithPlaybackState: StateFlow<Result<SessionWithPlaybackState?>> =
+        interactor.defaultActiveMediaSession
+            .filterData()
+            .flatMapLatest { session ->
+                if (session == null) {
+                    flowOf(null)
+                } else {
+                    mediaDeviceSessionInteractor.playbackState(session).mapNotNull { playback ->
+                        playback?.let { SessionWithPlaybackState(session, playback.isActive) }
+                    }
+                }
+            }
+            .wrapInResult()
+            .stateIn(
+                coroutineScope,
+                SharingStarted.Eagerly,
+                Result.Loading(),
+            )
+
+    private val currentAudioDevice: Flow<AudioOutputDevice> =
+        audioOutputInteractor.currentAudioDevice.filter { it !is AudioOutputDevice.Unknown }
+
+    val mediaOutputModel: StateFlow<Result<MediaOutputComponentModel>> =
+        audioModeInteractor.isOngoingCall
+            .flatMapLatest { isOngoingCall ->
+                audioOutputInteractor.isInAudioSharing.flatMapLatest { isInAudioSharing ->
+                    if (isOngoingCall) {
+                        currentAudioDevice.map {
+                            MediaOutputComponentModel.Calling(it, isInAudioSharing)
+                        }
+                    } else {
+                        combine(sessionWithPlaybackState.filterData(), currentAudioDevice) {
+                            sessionWithPlaybackState,
+                            currentAudioDevice ->
+                            if (sessionWithPlaybackState == null) {
+                                MediaOutputComponentModel.Idle(currentAudioDevice, isInAudioSharing)
+                            } else {
+                                MediaOutputComponentModel.MediaSession(
+                                    sessionWithPlaybackState.session,
+                                    sessionWithPlaybackState.isPlaybackActive,
+                                    currentAudioDevice,
+                                    isInAudioSharing,
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+            .wrapInResult()
+            .stateIn(coroutineScope, SharingStarted.Eagerly, Result.Loading())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaOutputComponentModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaOutputComponentModel.kt
new file mode 100644
index 0000000..220fb2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaOutputComponentModel.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.mediaoutput.domain.model
+
+import com.android.systemui.volume.domain.model.AudioOutputDevice
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
+
+/** Models domain data for the Media Output Component */
+sealed interface MediaOutputComponentModel {
+
+    val device: AudioOutputDevice
+    val isInAudioSharing: Boolean
+
+    /** There is an ongoing call on the device. */
+    data class Calling(
+        override val device: AudioOutputDevice,
+        override val isInAudioSharing: Boolean,
+    ) : MediaOutputComponentModel
+
+    /** There is media playing on the device. */
+    data class MediaSession(
+        val session: MediaDeviceSession,
+        val isPlaybackActive: Boolean,
+        override val device: AudioOutputDevice,
+        override val isInAudioSharing: Boolean,
+    ) : MediaOutputComponentModel
+
+    /** There is nothing playing on the device. */
+    data class Idle(
+        override val device: AudioOutputDevice,
+        override val isInAudioSharing: Boolean,
+    ) : MediaOutputComponentModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 40b7977..36b42f2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -18,33 +18,24 @@
 
 import android.content.Context
 import com.android.internal.logging.UiEventLogger
-import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Color
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.res.R
-import com.android.systemui.volume.domain.interactor.AudioOutputInteractor
 import com.android.systemui.volume.domain.model.AudioOutputDevice
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
-import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlaybackState
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputComponentInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaOutputComponentModel
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import com.android.systemui.volume.panel.shared.model.Result
 import com.android.systemui.volume.panel.shared.model.filterData
-import com.android.systemui.volume.panel.shared.model.wrapInResult
 import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.flow.stateIn
 
 /** Models the UI of the Media Output Volume Panel component. */
@@ -56,61 +47,40 @@
     private val context: Context,
     @VolumePanelScope private val coroutineScope: CoroutineScope,
     private val actionsInteractor: MediaOutputActionsInteractor,
-    private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
-    private val audioOutputInteractor: AudioOutputInteractor,
-    audioModeInteractor: AudioModeInteractor,
-    interactor: MediaOutputInteractor,
+    private val mediaOutputComponentInteractor: MediaOutputComponentInteractor,
     private val uiEventLogger: UiEventLogger,
 ) {
 
-    private val sessionWithPlaybackState: StateFlow<Result<SessionWithPlaybackState?>> =
-        interactor.defaultActiveMediaSession
-            .filterData()
-            .flatMapLatest { session ->
-                if (session == null) {
-                    flowOf(null)
-                } else {
-                    mediaDeviceSessionInteractor.playbackState(session).mapNotNull { playback ->
-                        playback?.let { SessionWithPlaybackState(session, playback.isActive) }
-                    }
-                }
-            }
-            .wrapInResult()
-            .stateIn(
-                coroutineScope,
-                SharingStarted.Eagerly,
-                Result.Loading(),
-            )
-
     val connectedDeviceViewModel: StateFlow<ConnectedDeviceViewModel?> =
-        combine(
-                sessionWithPlaybackState.filterData(),
-                audioModeInteractor.isOngoingCall,
-                audioOutputInteractor.currentAudioDevice.filter {
-                    it !is AudioOutputDevice.Unknown
-                },
-                audioOutputInteractor.isInAudioSharing,
-            ) { mediaDeviceSession, isOngoingCall, currentConnectedDevice, isInAudioSharing ->
+        mediaOutputComponentInteractor.mediaOutputModel
+            .filterData()
+            .map { mediaOutputModel ->
                 val label =
-                    when {
-                        isOngoingCall -> context.getString(R.string.media_output_title_ongoing_call)
-                        mediaDeviceSession?.isPlaybackActive == true ->
-                            context.getString(
-                                R.string.media_output_label_title,
-                                mediaDeviceSession.session.appLabel
-                            )
-                        else -> context.getString(R.string.media_output_title_without_playing)
+                    when (mediaOutputModel) {
+                        is MediaOutputComponentModel.Idle -> {
+                            context.getString(R.string.media_output_title_without_playing)
+                        }
+                        is MediaOutputComponentModel.MediaSession -> {
+                            if (mediaOutputModel.isPlaybackActive) {
+                                context.getString(
+                                    R.string.media_output_label_title,
+                                    mediaOutputModel.session.appLabel,
+                                )
+                            } else {
+                                context.getString(R.string.media_output_title_without_playing)
+                            }
+                        }
+                        is MediaOutputComponentModel.Calling -> {
+                            context.getString(R.string.media_output_title_ongoing_call)
+                        }
                     }
                 ConnectedDeviceViewModel(
                     label,
-                    when (isInAudioSharing) {
-                        true -> {
-                            context.getString(R.string.audio_sharing_description)
-                        }
-                        false -> {
-                            currentConnectedDevice.name
-                        }
-                    }
+                    if (mediaOutputModel.isInAudioSharing) {
+                        context.getString(R.string.audio_sharing_description)
+                    } else {
+                        mediaOutputModel.device.name
+                    },
                 )
             }
             .stateIn(
@@ -120,16 +90,20 @@
             )
 
     val deviceIconViewModel: StateFlow<DeviceIconViewModel?> =
-        combine(sessionWithPlaybackState.filterData(), audioOutputInteractor.currentAudioDevice) {
-                mediaDeviceSession,
-                currentConnectedDevice ->
+        mediaOutputComponentInteractor.mediaOutputModel
+            .filterData()
+            .map { mediaOutputModel ->
                 val icon: Icon =
-                    currentConnectedDevice
-                        .takeIf { currentConnectedDevice !is AudioOutputDevice.Unknown }
+                    mediaOutputModel.device
+                        .takeIf { it !is AudioOutputDevice.Unknown }
                         ?.icon
                         ?.let { Icon.Loaded(it, null) }
                         ?: Icon.Resource(R.drawable.ic_media_home_devices, null)
-                if (mediaDeviceSession?.isPlaybackActive == true) {
+                val isPlaybackActive =
+                    (mediaOutputModel as? MediaOutputComponentModel.MediaSession)
+                        ?.isPlaybackActive == true
+                val isCalling = mediaOutputModel is MediaOutputComponentModel.Calling
+                if (isPlaybackActive || isCalling) {
                     DeviceIconViewModel.IsPlaying(
                         icon = icon,
                         iconColor =
@@ -156,8 +130,9 @@
             )
 
     val enabled: StateFlow<Boolean> =
-        audioOutputInteractor.isInAudioSharing
-            .map { !it }
+        mediaOutputComponentInteractor.mediaOutputModel
+            .filterData()
+            .map { !it.isInAudioSharing }
             .stateIn(
                 coroutineScope,
                 SharingStarted.Eagerly,
@@ -166,7 +141,11 @@
 
     fun onBarClick(expandable: Expandable?) {
         uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_MEDIA_OUTPUT_CLICKED)
-        val result = sessionWithPlaybackState.value
-        actionsInteractor.onBarClick((result as? Result.Data)?.data, expandable)
+        val result: Result<MediaOutputComponentModel> =
+            mediaOutputComponentInteractor.mediaOutputModel.value
+        actionsInteractor.onBarClick(
+            (result as? Result.Data<MediaOutputComponentModel>)?.data,
+            expandable
+        )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
index 4b2d26a..6c6a1cc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
@@ -48,15 +48,31 @@
     private val uiEventLogger: UiEventLogger,
 ) {
 
+    private val spatialSpeakerIcon =
+        Icon.Resource(R.drawable.ic_spatial_speaker, contentDescription = null)
+
     val spatialAudioButton: StateFlow<ButtonViewModel?> =
-        interactor.isEnabled
-            .map {
-                val isChecked = it is SpatialAudioEnabledModel.SpatialAudioEnabled
-                it.toViewModel(isChecked)
+        combine(interactor.isEnabled, interactor.isAvailable) { isEnabled, isAvailable ->
+                isEnabled
+                    .toViewModel(
+                        isChecked = isEnabled is SpatialAudioEnabledModel.SpatialAudioEnabled,
+                        isHeadTrackingAvailable =
+                            isAvailable is SpatialAudioAvailabilityModel.SpatialAudio,
+                    )
                     .copy(label = context.getString(R.string.volume_panel_spatial_audio_title))
             }
             .stateIn(scope, SharingStarted.Eagerly, null)
 
+    val shouldUsePopup: StateFlow<Boolean> =
+        interactor.isAvailable
+            .map {
+                // head tracking availability means there are three possible states for the spatial
+                // audio: disabled, enabled regular, enabled with head tracking.
+                // Show popup in this case instead of a togglealbe button.
+                it is SpatialAudioAvailabilityModel.SpatialAudio
+            }
+            .stateIn(scope, SharingStarted.Eagerly, false)
+
     val isAvailable: StateFlow<Boolean> =
         availabilityCriteria.isAvailable().stateIn(scope, SharingStarted.Eagerly, true)
 
@@ -73,8 +89,12 @@
                         }
                     }
                     .map { isEnabled ->
-                        val isChecked = isEnabled == currentIsEnabled
-                        val buttonViewModel: ButtonViewModel = isEnabled.toViewModel(isChecked)
+                        val buttonViewModel: ButtonViewModel =
+                            isEnabled.toViewModel(
+                                isChecked = isEnabled == currentIsEnabled,
+                                isHeadTrackingAvailable =
+                                    isAvailable is SpatialAudioAvailabilityModel.HeadTracking,
+                            )
                         SpatialAudioButtonViewModel(button = buttonViewModel, model = isEnabled)
                     }
             }
@@ -97,11 +117,21 @@
         scope.launch { interactor.setEnabled(model) }
     }
 
-    private fun SpatialAudioEnabledModel.toViewModel(isChecked: Boolean): ButtonViewModel {
+    private fun SpatialAudioEnabledModel.toViewModel(
+        isChecked: Boolean,
+        isHeadTrackingAvailable: Boolean,
+    ): ButtonViewModel {
+        // This method deliberately uses the same icon for the case when head tracking is disabled
+        // to show a toggle button with a non-changing icon
         if (this is SpatialAudioEnabledModel.HeadTrackingEnabled) {
             return ButtonViewModel(
                 isActive = isChecked,
-                icon = Icon.Resource(R.drawable.ic_head_tracking, contentDescription = null),
+                icon =
+                    if (isHeadTrackingAvailable) {
+                        Icon.Resource(R.drawable.ic_head_tracking, contentDescription = null)
+                    } else {
+                        spatialSpeakerIcon
+                    },
                 label = context.getString(R.string.volume_panel_spatial_audio_tracking)
             )
         }
@@ -109,7 +139,12 @@
         if (this is SpatialAudioEnabledModel.SpatialAudioEnabled) {
             return ButtonViewModel(
                 isActive = isChecked,
-                icon = Icon.Resource(R.drawable.ic_spatial_audio, contentDescription = null),
+                icon =
+                    if (isHeadTrackingAvailable) {
+                        Icon.Resource(R.drawable.ic_spatial_audio, contentDescription = null)
+                    } else {
+                        spatialSpeakerIcon
+                    },
                 label = context.getString(R.string.volume_panel_spatial_audio_fixed)
             )
         }
@@ -117,7 +152,12 @@
         if (this is SpatialAudioEnabledModel.Disabled) {
             return ButtonViewModel(
                 isActive = isChecked,
-                icon = Icon.Resource(R.drawable.ic_spatial_audio_off, contentDescription = null),
+                icon =
+                    if (isHeadTrackingAvailable) {
+                        Icon.Resource(R.drawable.ic_spatial_audio_off, contentDescription = null)
+                    } else {
+                        spatialSpeakerIcon
+                    },
                 label = context.getString(R.string.volume_panel_spatial_audio_off)
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
index ff18418..1d32a4f 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
@@ -149,12 +149,12 @@
             if (event == WALLET_PREFERENCE_CHANGE && mWalletPreferenceObserver != null) {
                 mWalletPreferenceChangeEvents--;
                 if (mWalletPreferenceChangeEvents == 0) {
-                    mSecureSettings.unregisterContentObserver(mWalletPreferenceObserver);
+                    mSecureSettings.unregisterContentObserverSync(mWalletPreferenceObserver);
                 }
             } else if (event == DEFAULT_PAYMENT_APP_CHANGE && mDefaultPaymentAppObserver != null) {
                 mDefaultPaymentAppChangeEvents--;
                 if (mDefaultPaymentAppChangeEvents == 0) {
-                    mSecureSettings.unregisterContentObserver(mDefaultPaymentAppObserver);
+                    mSecureSettings.unregisterContentObserverSync(mDefaultPaymentAppObserver);
                 }
             } else if (event == DEFAULT_WALLET_APP_CHANGE && mDefaultWalletAppObserver != null) {
                 mDefaultWalletAppChangeEvents--;
@@ -312,7 +312,7 @@
                 }
             };
 
-            mSecureSettings.registerContentObserverForUser(
+            mSecureSettings.registerContentObserverForUserSync(
                     Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
                     false /* notifyForDescendants */,
                     mDefaultPaymentAppObserver,
@@ -351,7 +351,7 @@
                 }
             };
 
-            mSecureSettings.registerContentObserverForUser(
+            mSecureSettings.registerContentObserverForUserSync(
                     QuickAccessWalletClientImpl.SETTING_KEY,
                     false /* notifyForDescendants */,
                     mWalletPreferenceObserver,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 99b5a4b..cfa74f0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -181,7 +181,7 @@
                 ArgumentCaptor.forClass(ContentObserver.class);
         mController.init();
         mExecutor.runAllReady();
-        verify(mSecureSettings).registerContentObserverForUser(
+        verify(mSecureSettings).registerContentObserverForUserSync(
                 eq(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK),
                     anyBoolean(), observerCaptor.capture(), eq(UserHandle.USER_ALL));
         ContentObserver observer = observerCaptor.getValue();
@@ -247,7 +247,7 @@
                 ArgumentCaptor.forClass(ContentObserver.class);
         mController.init();
         mExecutor.runAllReady();
-        verify(mSecureSettings).registerContentObserverForUser(
+        verify(mSecureSettings).registerContentObserverForUserSync(
                 eq(Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED), anyBoolean(),
                     observerCaptor.capture(), eq(UserHandle.USER_ALL));
         ContentObserver observer = observerCaptor.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
index 92b06ba..138fed2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -393,7 +393,7 @@
 
         mWindowMagnificationSettings.showSettingPanel();
 
-        verify(mSecureSettings).registerContentObserverForUser(
+        verify(mSecureSettings).registerContentObserverForUserSync(
                 eq(ACCESSIBILITY_MAGNIFICATION_CAPABILITY),
                 any(ContentObserver.class),
                 eq(UserHandle.USER_CURRENT));
@@ -408,7 +408,7 @@
         mWindowMagnificationSettings.showSettingPanel();
         mWindowMagnificationSettings.hideSettingPanel();
 
-        verify(mSecureSettings).unregisterContentObserver(any(ContentObserver.class));
+        verify(mSecureSettings).unregisterContentObserverSync(any(ContentObserver.class));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
index 9ba56d2..6391986 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
@@ -82,7 +82,12 @@
             val keys =
                 captureMany<String> {
                     verify(secureSettings)
-                        .registerContentObserverForUser(capture(), anyBoolean(), any(), eq(USER_ID))
+                        .registerContentObserverForUserSync(
+                            capture(),
+                            anyBoolean(),
+                            any(),
+                            eq(USER_ID)
+                        )
                 }
 
             assertThat(keys).containsExactly(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION)
@@ -102,7 +107,7 @@
             val observer =
                 withArgCaptor<ContentObserver> {
                     verify(secureSettings)
-                        .registerContentObserverForUser(
+                        .registerContentObserverForUserSync(
                             eq(setting),
                             anyBoolean(),
                             capture(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
index 590989d..154c373 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
@@ -332,7 +332,7 @@
     }
 
     private fun attachRepositoryToSettings() {
-        secureSettings.registerContentObserver(
+        secureSettings.registerContentObserverSync(
             SETTING_SHOW,
             object : ContentObserver(null) {
                 override fun onChange(selfChange: Boolean) {
@@ -343,7 +343,7 @@
             }
         )
 
-        secureSettings.registerContentObserver(
+        secureSettings.registerContentObserverSync(
             SETTING_ACTION,
             object : ContentObserver(null) {
                 override fun onChange(selfChange: Boolean) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 16dd9af..e33d75c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -50,7 +50,6 @@
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
-import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.ActivityStarter
@@ -290,7 +289,6 @@
 
         underTest =
             KeyguardQuickAffordancesCombinedViewModel(
-                applicationScope = kosmos.applicationCoroutineScope,
                 quickAffordanceInteractor =
                     KeyguardQuickAffordanceInteractor(
                         keyguardInteractor = keyguardInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index 3ca5b42..f7b3f2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -196,7 +196,7 @@
         whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
         MediaPlayerData.clear()
         verify(globalSettings)
-            .registerContentObserver(
+            .registerContentObserverSync(
                 eq(Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE)),
                 capture(settingsObserverCaptor)
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 5e14b1a..820c3de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -175,7 +175,7 @@
                 mTileLifecycleManagerFactory, mUserFileManager, mQSPipelineFlagsRepository);
         mMainExecutor.runAllReady();
 
-        mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
+        mSecureSettings.registerContentObserverForUserSync(SETTING, new ContentObserver(null) {
             @Override
             public void onChange(boolean selfChange) {
                 super.onChange(selfChange);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index 8012768..dc9c22f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -320,7 +320,7 @@
                 .thenReturn(1);
         ArgumentCaptor<ContentObserver> contentObserverCaptor = ArgumentCaptor.forClass(
                 ContentObserver.class);
-        verify(mSecureSettings).registerContentObserverForUser(eq(SHOW_NOTIFICATION_SNOOZE),
+        verify(mSecureSettings).registerContentObserverForUserSync(eq(SHOW_NOTIFICATION_SNOOZE),
                 contentObserverCaptor.capture(), anyInt());
         ContentObserver contentObserver = contentObserverCaptor.getValue();
         contentObserver.onChange(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
index 7943872..2f77b33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
@@ -69,22 +69,21 @@
     private val groupMembershipManager: GroupMembershipManager = mock()
 
     private val section = NotifSection(mock(), 0)
-    private val entry = NotificationEntryBuilder()
-        .setSection(section)
-        .setParent(GroupEntry.ROOT_ENTRY)
-        .build()
+    private val entry =
+        NotificationEntryBuilder().setSection(section).setParent(GroupEntry.ROOT_ENTRY).build()
 
     private lateinit var contentObserver: ContentObserver
 
-    private val adjustmentProvider = NotifUiAdjustmentProvider(
-        handler,
-        secureSettings,
-        lockscreenUserManager,
-        sensitiveNotifProtectionController,
-        sectionStyleProvider,
-        userTracker,
-        groupMembershipManager,
-    )
+    private val adjustmentProvider =
+        NotifUiAdjustmentProvider(
+            handler,
+            secureSettings,
+            lockscreenUserManager,
+            sensitiveNotifProtectionController,
+            sectionStyleProvider,
+            userTracker,
+            groupMembershipManager,
+        )
 
     @Before
     fun setup() {
@@ -92,9 +91,8 @@
         adjustmentProvider.addDirtyListener(dirtyListener)
         verify(secureSettings).getIntForUser(eq(SHOW_NOTIFICATION_SNOOZE), any(), any())
         contentObserver = withArgCaptor {
-            verify(secureSettings).registerContentObserverForUser(
-                eq(SHOW_NOTIFICATION_SNOOZE), capture(), any()
-            )
+            verify(secureSettings)
+                .registerContentObserverForUserSync(eq(SHOW_NOTIFICATION_SNOOZE), capture(), any())
         }
         verifyNoMoreInteractions(secureSettings, dirtyListener)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
index 352b79f..310fa67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
@@ -102,6 +102,7 @@
         verify(userTracker).addCallback(any(), any())
         verify(dumpManager).registerNormalDumpable(anyString(), eq(controller))
     }
+
     @Test
     fun updateContentObserverRegistration_onUserChange_noSettingsListeners() {
         verify(userTracker).addCallback(capture(userTrackerCallbackCaptor), any())
@@ -112,10 +113,11 @@
         userCallback.onUserChanged(userId, context)
 
         // Validate: Nothing to do, since we aren't monitoring settings
-        verify(secureSettings, never()).unregisterContentObserver(any())
+        verify(secureSettings, never()).unregisterContentObserverSync(any())
         verify(secureSettings, never())
-            .registerContentObserverForUser(any(Uri::class.java), anyBoolean(), any(), anyInt())
+            .registerContentObserverForUserSync(any(Uri::class.java), anyBoolean(), any(), anyInt())
     }
+
     @Test
     fun updateContentObserverRegistration_onUserChange_withSettingsListeners() {
         // When: someone is listening to a setting
@@ -129,9 +131,9 @@
         userCallback.onUserChanged(userId, context)
 
         // Validate: The tracker is unregistered and re-registered with the new user
-        verify(secureSettings).unregisterContentObserver(any())
+        verify(secureSettings).unregisterContentObserverSync(any())
         verify(secureSettings)
-            .registerContentObserverForUser(eq(settingUri1), eq(false), any(), eq(userId))
+            .registerContentObserverForUserSync(eq(settingUri1), eq(false), any(), eq(userId))
     }
 
     @Test
@@ -140,7 +142,7 @@
         verifyZeroInteractions(secureSettings)
         testableLooper.processAllMessages()
         verify(secureSettings)
-            .registerContentObserverForUser(
+            .registerContentObserverForUserSync(
                 eq(settingUri1),
                 eq(false),
                 any(),
@@ -149,7 +151,7 @@
 
         controller.addCallback(settingUri1, Mockito.mock(Listener::class.java))
         verify(secureSettings)
-            .registerContentObserverForUser(any(Uri::class.java), anyBoolean(), any(), anyInt())
+            .registerContentObserverForUserSync(any(Uri::class.java), anyBoolean(), any(), anyInt())
     }
 
     @Test
@@ -158,7 +160,7 @@
         verifyZeroInteractions(secureSettings)
         testableLooper.processAllMessages()
         verify(secureSettings)
-            .registerContentObserverForUser(
+            .registerContentObserverForUserSync(
                 eq(settingUri1),
                 eq(false),
                 any(),
@@ -170,7 +172,7 @@
         verifyNoMoreInteractions(secureSettings)
         testableLooper.processAllMessages()
         verify(secureSettings)
-            .registerContentObserverForUser(
+            .registerContentObserverForUserSync(
                 eq(settingUri2),
                 eq(false),
                 any(),
@@ -186,7 +188,7 @@
         verifyZeroInteractions(secureSettings)
         testableLooper.processAllMessages()
         verify(secureSettings)
-            .registerContentObserverForUser(
+            .registerContentObserverForUserSync(
                 eq(settingUri1),
                 eq(false),
                 any(),
@@ -198,18 +200,18 @@
         verifyNoMoreInteractions(secureSettings)
         testableLooper.processAllMessages()
         verify(secureSettings)
-            .registerContentObserverForUser(eq(settingUri2), anyBoolean(), any(), anyInt())
+            .registerContentObserverForUserSync(eq(settingUri2), anyBoolean(), any(), anyInt())
         clearInvocations(secureSettings)
 
         controller.removeCallback(settingUri2, listenerSetting2)
         testableLooper.processAllMessages()
-        verify(secureSettings, never()).unregisterContentObserver(any())
+        verify(secureSettings, never()).unregisterContentObserverSync(any())
         clearInvocations(secureSettings)
 
         controller.removeCallback(settingUri1, listenerSetting1)
         verifyNoMoreInteractions(secureSettings)
         testableLooper.processAllMessages()
-        verify(secureSettings).unregisterContentObserver(any())
+        verify(secureSettings).unregisterContentObserverSync(any())
     }
 
     @Test
@@ -253,7 +255,7 @@
         testableLooper.processAllMessages()
 
         verify(secureSettings)
-            .registerContentObserverForUser(
+            .registerContentObserverForUserSync(
                 any(Uri::class.java),
                 anyBoolean(),
                 capture(settingsObserverCaptor),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 5ad88ad..d2c1dd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -202,7 +202,7 @@
         verify(mWakefulnessLifecycle).addObserver(mWakefulnessLifecycleObserver.capture());
         verify(mDumpManager).registerDumpable(any(), any());
         verify(mDeviceProvisionedController).addCallback(mDeviceProvisionedListener.capture());
-        verify(mSecureSettings).registerContentObserverForUser(
+        verify(mSecureSettings).registerContentObserverForUserSync(
                 eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES),
                 eq(false), mSettingsObserver.capture(), eq(UserHandle.USER_ALL)
         );
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
index 99f6303..25a44e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
@@ -83,7 +83,7 @@
 
     @Test
     public void testRegisterContentObserver() {
-        mFakeSettings.registerContentObserver("cat", mContentObserver);
+        mFakeSettings.registerContentObserverSync("cat", mContentObserver);
 
         mFakeSettings.putString("cat", "hat");
 
@@ -93,7 +93,7 @@
 
     @Test
     public void testRegisterContentObserverAllUsers() {
-        mFakeSettings.registerContentObserverForUser(
+        mFakeSettings.registerContentObserverForUserSync(
                 mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL);
 
         mFakeSettings.putString("cat", "hat");
@@ -104,8 +104,8 @@
 
     @Test
     public void testUnregisterContentObserver() {
-        mFakeSettings.registerContentObserver("cat", mContentObserver);
-        mFakeSettings.unregisterContentObserver(mContentObserver);
+        mFakeSettings.registerContentObserverSync("cat", mContentObserver);
+        mFakeSettings.unregisterContentObserverSync(mContentObserver);
 
         mFakeSettings.putString("cat", "hat");
 
@@ -115,9 +115,9 @@
 
     @Test
     public void testUnregisterContentObserverAllUsers() {
-        mFakeSettings.registerContentObserverForUser(
+        mFakeSettings.registerContentObserverForUserSync(
                 mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL);
-        mFakeSettings.unregisterContentObserver(mContentObserver);
+        mFakeSettings.unregisterContentObserverSync(mContentObserver);
 
         mFakeSettings.putString("cat", "hat");
 
@@ -128,7 +128,7 @@
     @Test
     public void testContentObserverDispatchCorrectUser() {
         int user = 10;
-        mFakeSettings.registerContentObserverForUser(
+        mFakeSettings.registerContentObserverForUserSync(
                 mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL
         );
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
index ab95707..eb11e38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
@@ -52,14 +52,14 @@
 
     @Test
     fun registerContentObserver_inputString_success() {
-        mSettings.registerContentObserver(TEST_SETTING, mContentObserver)
+        mSettings.registerContentObserverSync(TEST_SETTING, mContentObserver)
         verify(mSettings.getContentResolver())
             .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
     }
 
     @Test
     fun registerContentObserver_inputString_notifyForDescendants_true() {
-        mSettings.registerContentObserver(
+        mSettings.registerContentObserverSync(
             TEST_SETTING,
             notifyForDescendants = true,
             mContentObserver
@@ -70,14 +70,14 @@
 
     @Test
     fun registerContentObserver_inputUri_success() {
-        mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver)
+        mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver)
         verify(mSettings.getContentResolver())
             .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
     }
 
     @Test
     fun registerContentObserver_inputUri_notifyForDescendants_true() {
-        mSettings.registerContentObserver(
+        mSettings.registerContentObserverSync(
             TEST_SETTING_URI,
             notifyForDescendants = true,
             mContentObserver
@@ -88,7 +88,7 @@
 
     @Test
     fun unregisterContentObserver() {
-        mSettings.unregisterContentObserver(mContentObserver)
+        mSettings.unregisterContentObserverSync(mContentObserver)
         verify(mSettings.getContentResolver()).unregisterContentObserver(eq(mContentObserver))
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
index 56328b9..38469ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
@@ -58,7 +58,7 @@
 
     @Test
     fun registerContentObserverForUser_inputString_success() {
-        mSettings.registerContentObserverForUser(
+        mSettings.registerContentObserverForUserSync(
             TEST_SETTING,
             mContentObserver,
             mUserTracker.userId
@@ -74,7 +74,7 @@
 
     @Test
     fun registerContentObserverForUser_inputString_notifyForDescendants_true() {
-        mSettings.registerContentObserverForUser(
+        mSettings.registerContentObserverForUserSync(
             TEST_SETTING,
             notifyForDescendants = true,
             mContentObserver,
@@ -91,7 +91,7 @@
 
     @Test
     fun registerContentObserverForUser_inputUri_success() {
-        mSettings.registerContentObserverForUser(
+        mSettings.registerContentObserverForUserSync(
             TEST_SETTING_URI,
             mContentObserver,
             mUserTracker.userId
@@ -107,7 +107,7 @@
 
     @Test
     fun registerContentObserverForUser_inputUri_notifyForDescendants_true() {
-        mSettings.registerContentObserverForUser(
+        mSettings.registerContentObserverForUserSync(
             TEST_SETTING_URI,
             notifyForDescendants = true,
             mContentObserver,
@@ -124,14 +124,14 @@
 
     @Test
     fun registerContentObserver_inputUri_success() {
-        mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver)
+        mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver)
         verify(mSettings.getContentResolver())
             .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), eq(0))
     }
 
     @Test
     fun registerContentObserver_inputUri_notifyForDescendants_true() {
-        mSettings.registerContentObserver(
+        mSettings.registerContentObserverSync(
             TEST_SETTING_URI,
             notifyForDescendants = true,
             mContentObserver
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
index cd2710e..fb983f7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.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.kosmos.testDispatcher
 import com.android.systemui.settings.userTracker
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
 import com.android.systemui.util.mockito.mock
@@ -29,6 +30,7 @@
     CommunalSettingsInteractor(
         bgScope = applicationCoroutineScope,
         bgExecutor = fakeExecutor,
+        bgDispatcher = testDispatcher,
         repository = communalSettingsRepository,
         userInteractor = selectedUserInteractor,
         userTracker = userTracker,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
index beabaf5..3a70cdf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
@@ -44,7 +44,7 @@
     }
 
     @Override
-    public void registerContentObserver(Uri uri, boolean notifyDescendants,
+    public void registerContentObserverSync(Uri uri, boolean notifyDescendants,
             ContentObserver settingsObserver) {
         List<ContentObserver> observers;
         mContentObserversAllUsers.putIfAbsent(uri.toString(), new ArrayList<>());
@@ -53,7 +53,7 @@
     }
 
     @Override
-    public void unregisterContentObserver(ContentObserver settingsObserver) {
+    public void unregisterContentObserverSync(ContentObserver settingsObserver) {
         for (Map.Entry<String, List<ContentObserver>> entry :
                 mContentObserversAllUsers.entrySet()) {
             entry.getValue().remove(settingsObserver);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
index a491886..cd219ec 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
@@ -66,7 +66,7 @@
     }
 
     @Override
-    public void registerContentObserverForUser(Uri uri, boolean notifyDescendants,
+    public void registerContentObserverForUserSync(Uri uri, boolean notifyDescendants,
             ContentObserver settingsObserver, int userHandle) {
         List<ContentObserver> observers;
         if (userHandle == UserHandle.USER_ALL) {
@@ -81,7 +81,7 @@
     }
 
     @Override
-    public void unregisterContentObserver(ContentObserver settingsObserver) {
+    public void unregisterContentObserverSync(ContentObserver settingsObserver) {
         for (SettingsKey key : mContentObservers.keySet()) {
             List<ContentObserver> observers = mContentObservers.get(key);
             observers.remove(settingsObserver);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt
index 3f51a79..e2d414e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.volume.domain.interactor
 
+import android.content.applicationContext
 import com.android.systemui.bluetooth.bluetoothAdapter
 import com.android.systemui.bluetooth.localBluetoothManager
 import com.android.systemui.kosmos.Kosmos
@@ -27,6 +28,7 @@
 val Kosmos.audioOutputInteractor by
     Kosmos.Fixture {
         AudioOutputInteractor(
+            applicationContext,
             audioRepository,
             audioModeInteractor,
             testScope.backgroundScope,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt
new file mode 100644
index 0000000..9f11822
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.volume.domain.interactor.audioModeInteractor
+import com.android.systemui.volume.domain.interactor.audioOutputInteractor
+import com.android.systemui.volume.mediaDeviceSessionInteractor
+import com.android.systemui.volume.mediaOutputInteractor
+
+val Kosmos.mediaOutputComponentInteractor by
+    Kosmos.Fixture {
+        MediaOutputComponentInteractor(
+            testScope.backgroundScope,
+            mediaDeviceSessionInteractor,
+            audioOutputInteractor,
+            audioModeInteractor,
+            mediaOutputInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt
index 6d4576e..2cd6ff2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt
@@ -20,11 +20,8 @@
 import com.android.internal.logging.uiEventLogger
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.volume.domain.interactor.audioModeInteractor
-import com.android.systemui.volume.domain.interactor.audioOutputInteractor
-import com.android.systemui.volume.mediaDeviceSessionInteractor
 import com.android.systemui.volume.mediaOutputActionsInteractor
-import com.android.systemui.volume.mediaOutputInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.mediaOutputComponentInteractor
 
 var Kosmos.mediaOutputViewModel by
     Kosmos.Fixture {
@@ -32,10 +29,7 @@
             applicationContext,
             testScope.backgroundScope,
             mediaOutputActionsInteractor,
-            mediaDeviceSessionInteractor,
-            audioOutputInteractor,
-            audioModeInteractor,
-            mediaOutputInteractor,
+            mediaOutputComponentInteractor,
             uiEventLogger,
         )
     }
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 2184340..b9cdf27 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -1005,7 +1005,7 @@
         if (isForeground || foregroundId != 0) {
             pw.print(prefix); pw.print("isForeground="); pw.print(isForeground);
             pw.print(" foregroundId="); pw.print(foregroundId);
-            pw.printf(" types=%08X", foregroundServiceType);
+            pw.printf(" types=0x%08X", foregroundServiceType);
             pw.print(" foregroundNoti="); pw.println(foregroundNoti);
 
             if (isShortFgs() && mShortFgsInfo != null) {
diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 5690a9e..69bc66f 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -135,7 +135,7 @@
             float[] alternativeRefreshRates,
             @Display.HdrCapabilities.HdrType int[] supportedHdrTypes) {
         return new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), width, height, refreshRate,
-                vsyncRate, false, alternativeRefreshRates, supportedHdrTypes);
+                vsyncRate, /* isSynthetic= */ false, alternativeRefreshRates, supportedHdrTypes);
     }
 
     public interface Listener {
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index baa154d..184ae41 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -47,6 +47,8 @@
     private final BrightnessEvent mBrightnessEvent;
     private final int mBrightnessAdjustmentFlag;
 
+    private final boolean mIsUserInitiatedChange;
+
     private DisplayBrightnessState(Builder builder) {
         mBrightness = builder.getBrightness();
         mSdrBrightness = builder.getSdrBrightness();
@@ -60,6 +62,7 @@
         mShouldUpdateScreenBrightnessSetting = builder.shouldUpdateScreenBrightnessSetting();
         mBrightnessEvent = builder.getBrightnessEvent();
         mBrightnessAdjustmentFlag = builder.getBrightnessAdjustmentFlag();
+        mIsUserInitiatedChange = builder.isUserInitiatedChange();
     }
 
     /**
@@ -148,6 +151,13 @@
         return mBrightnessAdjustmentFlag;
     }
 
+    /**
+     * Gets if the current brightness changes are because of a user initiated change
+     */
+    public boolean isUserInitiatedChange() {
+        return mIsUserInitiatedChange;
+    }
+
     @Override
     public String toString() {
         StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
@@ -168,6 +178,7 @@
         stringBuilder.append("\n    mBrightnessEvent:")
                 .append(Objects.toString(mBrightnessEvent, "null"));
         stringBuilder.append("\n    mBrightnessAdjustmentFlag:").append(mBrightnessAdjustmentFlag);
+        stringBuilder.append("\n    mIsUserInitiatedChange:").append(mIsUserInitiatedChange);
         return stringBuilder.toString();
     }
 
@@ -199,7 +210,8 @@
                 && mShouldUpdateScreenBrightnessSetting
                     == otherState.shouldUpdateScreenBrightnessSetting()
                 && Objects.equals(mBrightnessEvent, otherState.getBrightnessEvent())
-                && mBrightnessAdjustmentFlag == otherState.getBrightnessAdjustmentFlag();
+                && mBrightnessAdjustmentFlag == otherState.getBrightnessAdjustmentFlag()
+                && mIsUserInitiatedChange == otherState.isUserInitiatedChange();
     }
 
     @Override
@@ -207,7 +219,8 @@
         return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason,
                 mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mMinBrightness,
                 mCustomAnimationRate,
-                mShouldUpdateScreenBrightnessSetting, mBrightnessEvent, mBrightnessAdjustmentFlag);
+                mShouldUpdateScreenBrightnessSetting, mBrightnessEvent, mBrightnessAdjustmentFlag,
+                mIsUserInitiatedChange);
     }
 
     /**
@@ -236,6 +249,8 @@
 
         public int mBrightnessAdjustmentFlag = 0;
 
+        private boolean mIsUserInitiatedChange;
+
         /**
          * Create a builder starting with the values from the specified {@link
          * DisplayBrightnessState}.
@@ -257,6 +272,7 @@
                     state.shouldUpdateScreenBrightnessSetting());
             builder.setBrightnessEvent(state.getBrightnessEvent());
             builder.setBrightnessAdjustmentFlag(state.getBrightnessAdjustmentFlag());
+            builder.setIsUserInitiatedChange(state.isUserInitiatedChange());
             return builder;
         }
 
@@ -464,5 +480,20 @@
             mBrightnessAdjustmentFlag = brightnessAdjustmentFlag;
             return this;
         }
+
+        /**
+         * Gets if the current change is a user initiated change
+         */
+        public boolean isUserInitiatedChange() {
+            return mIsUserInitiatedChange;
+        }
+
+        /**
+         * This is used to set if the current change is a user initiated change
+         */
+        public Builder setIsUserInitiatedChange(boolean isUserInitiatedChange) {
+            mIsUserInitiatedChange = isUserInitiatedChange;
+            return this;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index d4c0b01..5fd0253 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -49,6 +49,8 @@
 import static android.provider.Settings.Secure.RESOLUTION_MODE_HIGH;
 import static android.provider.Settings.Secure.RESOLUTION_MODE_UNKNOWN;
 
+import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
+
 import android.Manifest;
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
@@ -4972,8 +4974,9 @@
                 }
 
                 final DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked();
-                final boolean ownContent = (displayDevice.getDisplayDeviceInfoLocked().flags
-                        & DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY) != 0;
+                final boolean isRearDisplay = display.getDevicePositionLocked() == POSITION_REAR;
+                final boolean ownContent = ((displayDevice.getDisplayDeviceInfoLocked().flags
+                        & DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY) != 0) || isRearDisplay;
                 // If the display has enabled mirroring, but specified that it will be managed by
                 // WindowManager, return an invalid display id. This is to ensure we don't
                 // accidentally select the display id to mirror based on DM logic and instead allow
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index b97053b..8d71c70 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1391,6 +1391,7 @@
         // request changes.
         final boolean wasShortTermModelActive =
                 mAutomaticBrightnessStrategy.isShortTermModelActive();
+        boolean userInitiatedChange = displayBrightnessState.isUserInitiatedChange();
         boolean allowAutoBrightnessWhileDozing =
                 mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig();
         if (mFlags.offloadControlsDozeAutoBrightness() && mFlags.isDisplayOffloadEnabled()
@@ -1413,13 +1414,14 @@
                     mPowerRequest.policy,
                     mDisplayBrightnessController.getLastUserSetScreenBrightness(),
                     userSetBrightnessChanged);
+
+            // If the brightness is already set then it's been overridden by something other than
+            // the user, or is a temporary adjustment.
+            userInitiatedChange = (Float.isNaN(brightnessState))
+                    && (mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged()
+                    || userSetBrightnessChanged);
         }
 
-        // If the brightness is already set then it's been overridden by something other than the
-        // user, or is a temporary adjustment.
-        boolean userInitiatedChange = (Float.isNaN(brightnessState))
-                && (mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged()
-                || userSetBrightnessChanged);
 
         final int autoBrightnessState = mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
                 ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index aa17df6..d567331 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -600,6 +600,7 @@
     private StrategyExecutionRequest constructStrategyExecutionRequest(
             DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
         float currentScreenBrightness = getCurrentBrightness();
-        return new StrategyExecutionRequest(displayPowerRequest, currentScreenBrightness);
+        return new StrategyExecutionRequest(displayPowerRequest, currentScreenBrightness,
+                mUserSetScreenBrightnessUpdated);
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java b/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java
index 82c0bbf..304640b 100644
--- a/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java
+++ b/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java
@@ -29,10 +29,14 @@
 
     private final float mCurrentScreenBrightness;
 
+    // Represents if the user set screen brightness was changed or not.
+    private boolean mUserSetBrightnessChanged;
+
     public StrategyExecutionRequest(DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
-            float currentScreenBrightness) {
+            float currentScreenBrightness, boolean userSetBrightnessChanged) {
         mDisplayPowerRequest = displayPowerRequest;
         mCurrentScreenBrightness = currentScreenBrightness;
+        mUserSetBrightnessChanged = userSetBrightnessChanged;
     }
 
     public DisplayManagerInternal.DisplayPowerRequest getDisplayPowerRequest() {
@@ -43,6 +47,10 @@
         return mCurrentScreenBrightness;
     }
 
+    public boolean isUserSetBrightnessChanged() {
+        return mUserSetBrightnessChanged;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (!(obj instanceof StrategyExecutionRequest)) {
@@ -50,11 +58,13 @@
         }
         StrategyExecutionRequest other = (StrategyExecutionRequest) obj;
         return Objects.equals(mDisplayPowerRequest, other.getDisplayPowerRequest())
-                && mCurrentScreenBrightness == other.getCurrentScreenBrightness();
+                && mCurrentScreenBrightness == other.getCurrentScreenBrightness()
+                && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged();
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mDisplayPowerRequest, mCurrentScreenBrightness);
+        return Objects.hash(mDisplayPowerRequest, mCurrentScreenBrightness,
+                mUserSetBrightnessChanged);
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index 2907364..2b5241f 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -290,6 +290,8 @@
                 .setBrightnessAdjustmentFlag(mAutoBrightnessAdjustmentReasonsFlags)
                 .setShouldUpdateScreenBrightnessSetting(
                         brightness != strategyExecutionRequest.getCurrentScreenBrightness())
+                .setIsUserInitiatedChange(getAutoBrightnessAdjustmentChanged()
+                        || strategyExecutionRequest.isUserSetBrightnessChanged())
                 .build();
     }
 
diff --git a/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java
index 3463649a..0b92317 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java
@@ -45,6 +45,7 @@
                 // The fallback brightness might change due to clamping. Make sure we tell the rest
                 // of the system by updating the setting
                 .setShouldUpdateScreenBrightnessSetting(true)
+                .setIsUserInitiatedChange(strategyExecutionRequest.isUserSetBrightnessChanged())
                 .build();
     }
 
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index f923cbc9..f56d803 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -353,7 +353,7 @@
     }
 
     /**
-     * @return Whether to ignore preferredRefreshRate app request or not
+     * @return Whether to ignore preferredRefreshRate app request conversion to display mode or not
      */
     public boolean ignoreAppPreferredRefreshRateRequest() {
         return mIgnoreAppPreferredRefreshRate.isEnabled();
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 76a2827..d519748 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -1254,13 +1254,9 @@
      *  Responsible for keeping track of app requested refresh rates per display
      */
     public final class AppRequestObserver {
-        private final SparseArray<Display.Mode> mAppRequestedModeByDisplay;
-        private final SparseArray<RefreshRateRange> mAppPreferredRefreshRateRangeByDisplay;
         private final boolean mIgnorePreferredRefreshRate;
 
         AppRequestObserver(DisplayManagerFlags flags) {
-            mAppRequestedModeByDisplay = new SparseArray<>();
-            mAppPreferredRefreshRateRangeByDisplay = new SparseArray<>();
             mIgnorePreferredRefreshRate = flags.ignoreAppPreferredRefreshRateRequest();
         }
 
@@ -1269,34 +1265,77 @@
          */
         public void setAppRequest(int displayId, int modeId, float requestedRefreshRate,
                 float requestedMinRefreshRateRange, float requestedMaxRefreshRateRange) {
+            Display.Mode requestedMode;
+            synchronized (mLock) {
+                requestedMode = findModeLocked(displayId, modeId, requestedRefreshRate);
+            }
 
-            if (modeId == 0 && requestedRefreshRate != 0 && !mIgnorePreferredRefreshRate) {
+            Vote frameRateVote = getFrameRateVote(
+                    requestedMinRefreshRateRange, requestedMaxRefreshRateRange);
+            Vote baseModeRefreshRateVote = getBaseModeVote(requestedMode, requestedRefreshRate);
+            Vote sizeVote = getSizeVote(requestedMode);
+
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
+                    frameRateVote);
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                    baseModeRefreshRateVote);
+            mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
+        }
+
+        private Display.Mode findModeLocked(int displayId, int modeId, float requestedRefreshRate) {
+            Display.Mode mode = null;
+            if (modeId != 0) {
+                mode = findAppModeByIdLocked(displayId, modeId);
+            } else if (requestedRefreshRate != 0 && !mIgnorePreferredRefreshRate) { // modeId == 0
                 // Scan supported modes returned to find a mode with the same
                 // size as the default display mode but with the specified refresh rate instead.
-                Display.Mode mode = findDefaultModeByRefreshRate(displayId, requestedRefreshRate);
-                if (mode != null) {
-                    modeId = mode.getModeId();
-                } else {
+                mode = findDefaultModeByRefreshRateLocked(displayId, requestedRefreshRate);
+                if (mode == null) {
                     Slog.e(TAG, "Couldn't find a mode for the requestedRefreshRate: "
                             + requestedRefreshRate + " on Display: " + displayId);
                 }
             }
+            return mode;
+        }
 
-            synchronized (mLock) {
-                setAppRequestedModeLocked(displayId, modeId);
-                setAppPreferredRefreshRateRangeLocked(displayId, requestedMinRefreshRateRange,
-                        requestedMaxRefreshRateRange);
+        private Vote getFrameRateVote(float minRefreshRate, float maxRefreshRate) {
+            RefreshRateRange refreshRateRange = null;
+            if (minRefreshRate > 0 || maxRefreshRate > 0) {
+                float max = maxRefreshRate > 0
+                        ? maxRefreshRate : Float.POSITIVE_INFINITY;
+                refreshRateRange = new RefreshRateRange(minRefreshRate, max);
+                if (refreshRateRange.min == 0 && refreshRateRange.max == 0) {
+                    // minRefreshRate/maxRefreshRate were invalid
+                    refreshRateRange = null;
+                }
             }
+            return refreshRateRange != null
+                    ? Vote.forRenderFrameRates(refreshRateRange.min, refreshRateRange.max) : null;
+        }
+
+        private Vote getSizeVote(@Nullable Display.Mode mode) {
+            return mode != null
+                    ?  Vote.forSize(mode.getPhysicalWidth(), mode.getPhysicalHeight()) : null;
+        }
+
+        private Vote getBaseModeVote(@Nullable Display.Mode mode, float requestedRefreshRate) {
+            Vote vote = null;
+            if (mode != null) {
+                if (mode.isSynthetic()) {
+                    vote = Vote.forRequestedRefreshRate(mode.getRefreshRate());
+                } else {
+                    vote = Vote.forBaseModeRefreshRate(mode.getRefreshRate());
+                }
+            } else if (requestedRefreshRate != 0f && mIgnorePreferredRefreshRate) {
+                vote = Vote.forRequestedRefreshRate(requestedRefreshRate);
+            } // !mIgnorePreferredRefreshRate case is handled by findModeLocked
+            return vote;
         }
 
         @Nullable
-        private Display.Mode findDefaultModeByRefreshRate(int displayId, float refreshRate) {
-            Display.Mode[] modes;
-            Display.Mode defaultMode;
-            synchronized (mLock) {
-                modes = mAppSupportedModesByDisplay.get(displayId);
-                defaultMode = mDefaultModeByDisplay.get(displayId);
-            }
+        private Display.Mode findDefaultModeByRefreshRateLocked(int displayId, float refreshRate) {
+            Display.Mode[] modes = mAppSupportedModesByDisplay.get(displayId);
+            Display.Mode defaultMode = mDefaultModeByDisplay.get(displayId);
             for (int i = 0; i < modes.length; i++) {
                 if (modes[i].matches(defaultMode.getPhysicalWidth(),
                         defaultMode.getPhysicalHeight(), refreshRate)) {
@@ -1306,69 +1345,6 @@
             return null;
         }
 
-        private void setAppRequestedModeLocked(int displayId, int modeId) {
-            final Display.Mode requestedMode = findAppModeByIdLocked(displayId, modeId);
-            if (Objects.equals(requestedMode, mAppRequestedModeByDisplay.get(displayId))) {
-                return;
-            }
-            final Vote baseModeRefreshRateVote;
-            final Vote sizeVote;
-            if (requestedMode != null) {
-                mAppRequestedModeByDisplay.put(displayId, requestedMode);
-                sizeVote = Vote.forSize(requestedMode.getPhysicalWidth(),
-                        requestedMode.getPhysicalHeight());
-                if (requestedMode.isSynthetic()) {
-                    // TODO: for synthetic mode we should not limit frame rate, we must ensure
-                    // that frame rate is reachable within other Votes constraints
-                    baseModeRefreshRateVote = Vote.forRenderFrameRates(
-                            requestedMode.getRefreshRate(), requestedMode.getRefreshRate());
-                } else {
-                    baseModeRefreshRateVote =
-                            Vote.forBaseModeRefreshRate(requestedMode.getRefreshRate());
-                }
-            } else {
-                mAppRequestedModeByDisplay.remove(displayId);
-                baseModeRefreshRateVote = null;
-                sizeVote = null;
-            }
-
-            mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                    baseModeRefreshRateVote);
-            mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
-        }
-
-        private void setAppPreferredRefreshRateRangeLocked(int displayId,
-                float requestedMinRefreshRateRange, float requestedMaxRefreshRateRange) {
-            final Vote vote;
-
-            RefreshRateRange refreshRateRange = null;
-            if (requestedMinRefreshRateRange > 0 || requestedMaxRefreshRateRange > 0) {
-                float min = requestedMinRefreshRateRange;
-                float max = requestedMaxRefreshRateRange > 0
-                        ? requestedMaxRefreshRateRange : Float.POSITIVE_INFINITY;
-                refreshRateRange = new RefreshRateRange(min, max);
-                if (refreshRateRange.min == 0 && refreshRateRange.max == 0) {
-                    // requestedMinRefreshRateRange/requestedMaxRefreshRateRange were invalid
-                    refreshRateRange = null;
-                }
-            }
-
-            if (Objects.equals(refreshRateRange,
-                    mAppPreferredRefreshRateRangeByDisplay.get(displayId))) {
-                return;
-            }
-
-            if (refreshRateRange != null) {
-                mAppPreferredRefreshRateRangeByDisplay.put(displayId, refreshRateRange);
-                vote = Vote.forRenderFrameRates(refreshRateRange.min, refreshRateRange.max);
-            } else {
-                mAppPreferredRefreshRateRangeByDisplay.remove(displayId);
-                vote = null;
-            }
-            mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
-                    vote);
-        }
-
         private Display.Mode findAppModeByIdLocked(int displayId, int modeId) {
             Display.Mode[] modes = mAppSupportedModesByDisplay.get(displayId);
             if (modes == null) {
@@ -1384,19 +1360,7 @@
 
         private void dumpLocked(PrintWriter pw) {
             pw.println("  AppRequestObserver");
-            pw.println("    mAppRequestedModeByDisplay:");
-            for (int i = 0; i < mAppRequestedModeByDisplay.size(); i++) {
-                final int id = mAppRequestedModeByDisplay.keyAt(i);
-                final Display.Mode mode = mAppRequestedModeByDisplay.valueAt(i);
-                pw.println("    " + id + " -> " + mode);
-            }
-            pw.println("    mAppPreferredRefreshRateRangeByDisplay:");
-            for (int i = 0; i < mAppPreferredRefreshRateRangeByDisplay.size(); i++) {
-                final int id = mAppPreferredRefreshRateRangeByDisplay.keyAt(i);
-                final RefreshRateRange refreshRateRange =
-                        mAppPreferredRefreshRateRangeByDisplay.valueAt(i);
-                pw.println("    " + id + " -> " + refreshRateRange);
-            }
+            pw.println("    mIgnorePreferredRefreshRate: " + mIgnorePreferredRefreshRate);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/mode/RequestedRefreshRateVote.java b/services/core/java/com/android/server/display/mode/RequestedRefreshRateVote.java
new file mode 100644
index 0000000..843cf88
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/RequestedRefreshRateVote.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.mode;
+
+import android.annotation.NonNull;
+
+import java.util.Objects;
+
+class RequestedRefreshRateVote implements Vote {
+    final float mRefreshRate;
+
+    RequestedRefreshRateVote(float refreshRate) {
+        this.mRefreshRate = refreshRate;
+    }
+
+    @Override
+    public void updateSummary(@NonNull VoteSummary summary) {
+        summary.requestedRefreshRates.add(mRefreshRate);
+    }
+
+    @Override
+    public String toString() {
+        return "RequestedRefreshRateVote{ refreshRate=" + mRefreshRate + " }";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof RequestedRefreshRateVote that)) return false;
+        return Float.compare(mRefreshRate, that.mRefreshRate) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mRefreshRate);
+    }
+}
diff --git a/services/core/java/com/android/server/display/mode/SyntheticModeManager.java b/services/core/java/com/android/server/display/mode/SyntheticModeManager.java
index 5b6bbc5..a83b939 100644
--- a/services/core/java/com/android/server/display/mode/SyntheticModeManager.java
+++ b/services/core/java/com/android/server/display/mode/SyntheticModeManager.java
@@ -32,7 +32,9 @@
  */
 public class SyntheticModeManager {
     private static final float FLOAT_TOLERANCE = 0.01f;
-    private static final float SYNTHETIC_MODE_HIGH_BOUNDARY = 60f + FLOAT_TOLERANCE;
+    private static final float SYNTHETIC_MODE_REFRESH_RATE = 60f;
+    private static final float SYNTHETIC_MODE_HIGH_BOUNDARY =
+            SYNTHETIC_MODE_REFRESH_RATE + FLOAT_TOLERANCE;
 
     private final boolean mSynthetic60HzModesEnabled;
 
@@ -62,7 +64,7 @@
                 nextModeId = mode.getModeId();
             }
 
-            float divisor = mode.getVsyncRate() / 60f;
+            float divisor = mode.getVsyncRate() / SYNTHETIC_MODE_REFRESH_RATE;
             boolean is60HzAchievable = Math.abs(divisor - Math.round(divisor)) < FLOAT_TOLERANCE;
             if (is60HzAchievable) {
                 sizes.put(new Size(mode.getPhysicalWidth(), mode.getPhysicalHeight()),
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
index 3e8b3c5..1ec469c 100644
--- a/services/core/java/com/android/server/display/mode/Vote.java
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -186,6 +186,10 @@
         return new BaseModeRefreshRateVote(baseModeRefreshRate);
     }
 
+    static Vote forRequestedRefreshRate(float refreshRate) {
+        return new RequestedRefreshRateVote(refreshRate);
+    }
+
     static Vote forSupportedRefreshRates(List<SupportedModeData> supportedModes) {
         if (supportedModes.isEmpty()) {
             return null;
diff --git a/services/core/java/com/android/server/display/mode/VoteSummary.java b/services/core/java/com/android/server/display/mode/VoteSummary.java
index d4ce892..00a9226 100644
--- a/services/core/java/com/android/server/display/mode/VoteSummary.java
+++ b/services/core/java/com/android/server/display/mode/VoteSummary.java
@@ -23,7 +23,9 @@
 import android.view.SurfaceControl;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 final class VoteSummary {
     private static final float FLOAT_TOLERANCE = SurfaceControl.RefreshRateRange.FLOAT_TOLERANCE;
@@ -38,7 +40,14 @@
     public int minWidth;
     public int minHeight;
     public boolean disableRefreshRateSwitching;
+    /**
+     *  available modes should have mode with specific refresh rate
+     */
     public float appRequestBaseModeRefreshRate;
+    /**
+     * requestRefreshRate should be within [minRenderFrameRate, maxRenderFrameRate] range
+     */
+    public Set<Float> requestedRefreshRates = new HashSet<>();
 
     @Nullable
     public List<SupportedRefreshRatesVote.RefreshRates> supportedRefreshRates;
@@ -329,6 +338,21 @@
             }
             return false;
         }
+
+        for (Float requestedRefreshRate : requestedRefreshRates) {
+            if (requestedRefreshRate < minRenderFrameRate
+                    || requestedRefreshRate > maxRenderFrameRate) {
+                if (mLoggingEnabled) {
+                    Slog.w(TAG, "Requested refreshRate is outside frame rate range"
+                            + ": requestedRefreshRates=" + requestedRefreshRates
+                            + ", requestedRefreshRate=" + requestedRefreshRate
+                            + ", minRenderFrameRate=" + minRenderFrameRate
+                            + ", maxRenderFrameRate=" + maxRenderFrameRate);
+                }
+                return false;
+            }
+        }
+
         return true;
     }
 
@@ -370,6 +394,7 @@
         minHeight = 0;
         disableRefreshRateSwitching = false;
         appRequestBaseModeRefreshRate = 0f;
+        requestedRefreshRates.clear();
         supportedRefreshRates = null;
         supportedModeIds = null;
         if (mLoggingEnabled) {
@@ -393,6 +418,7 @@
                 + ", minHeight=" + minHeight
                 + ", disableRefreshRateSwitching=" + disableRefreshRateSwitching
                 + ", appRequestBaseModeRefreshRate=" + appRequestBaseModeRefreshRate
+                + ", requestRefreshRates=" + requestedRefreshRates
                 + ", supportedRefreshRates=" + supportedRefreshRates
                 + ", supportedModeIds=" + supportedModeIds
                 + ", mIsDisplayResolutionRangeVotingEnabled="
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index d4bbe84..61022cc 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1346,10 +1346,13 @@
 
             // In a multi-resumed environment, like in a freeform device, the top
             // activity can be resumed, but it might not be the focused app.
-            // Set focused app when top activity is resumed
-            if (taskDisplayArea.inMultiWindowMode() && taskDisplayArea.mDisplayContent != null
-                    && taskDisplayArea.mDisplayContent.mFocusedApp != next) {
-                taskDisplayArea.mDisplayContent.setFocusedApp(next);
+            // Set focused app when top activity is resumed. However, we shouldn't do it for a
+            // same task because it can break focused state. (e.g. activity embedding)
+            if (taskDisplayArea.inMultiWindowMode() && taskDisplayArea.mDisplayContent != null) {
+                final ActivityRecord focusedApp = taskDisplayArea.mDisplayContent.mFocusedApp;
+                if (focusedApp == null || focusedApp.getTask() != next.getTask()) {
+                    taskDisplayArea.mDisplayContent.setFocusedApp(next);
+                }
             }
             ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Top activity "
                     + "resumed %s", next);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
index 7483b43..c7fd979 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
@@ -202,9 +202,9 @@
     private String getDefaultSmsPackage() {
         //TODO(b/319449037): Unflag the following change.
         if (Flags.defaultSmsPersonalAppSuspensionFixEnabled()) {
-            return SmsApplication.getDefaultSmsApplicationAsUser(
-                            mContext, /*updateIfNeeded=*/ false, mContext.getUser())
-                    .getPackageName();
+            ComponentName defaultSmsApp = SmsApplication.getDefaultSmsApplicationAsUser(
+                    mContext, /*updateIfNeeded=*/ false, mContext.getUser());
+            return defaultSmsApp != null ? defaultSmsApp.getPackageName() : null;
         } else {
             return Telephony.Sms.getDefaultSmsPackage(mContext);
         }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index 8db896b..bba4c8d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -58,6 +58,7 @@
                 .setShouldUseAutoBrightness(shouldUseAutoBrightness)
                 .setShouldUpdateScreenBrightnessSetting(shouldUpdateScreenBrightnessSetting)
                 .setBrightnessAdjustmentFlag(brightnessAdjustmentFlag)
+                .setIsUserInitiatedChange(true)
                 .build();
 
         assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA);
@@ -109,7 +110,9 @@
                 .append("\n    mBrightnessEvent:")
                 .append(Objects.toString(displayBrightnessState.getBrightnessEvent(), "null"))
                 .append("\n    mBrightnessAdjustmentFlag:")
-                .append(displayBrightnessState.getBrightnessAdjustmentFlag());
+                .append(displayBrightnessState.getBrightnessAdjustmentFlag())
+                .append("\n    mIsUserInitiatedChange:")
+                .append(displayBrightnessState.isUserInitiatedChange());
         return sb.toString();
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index c5105e7..323ef6a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -121,7 +121,8 @@
                 any(StrategySelectionRequest.class))).thenReturn(displayBrightnessStrategy);
         mDisplayBrightnessController.updateBrightness(displayPowerRequest, targetDisplayState);
         verify(displayBrightnessStrategy).updateBrightness(
-                eq(new StrategyExecutionRequest(displayPowerRequest, DEFAULT_BRIGHTNESS)));
+                eq(new StrategyExecutionRequest(displayPowerRequest, DEFAULT_BRIGHTNESS,
+                        /* userSetBrightnessChanged= */ false)));
         assertEquals(mDisplayBrightnessController.getCurrentDisplayBrightnessStrategy(),
                 displayBrightnessStrategy);
     }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java
index 7a6a911..4d5ff55 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java
@@ -115,7 +115,8 @@
                         .build();
         DisplayBrightnessState updatedDisplayBrightnessState =
                 mAutoBrightnessFallbackStrategy.updateBrightness(
-                        new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+                        new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+                                /* userSetBrightnessChanged= */ false));
         assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index afb5a5c..8a33f34 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -513,9 +513,11 @@
                 .setBrightnessEvent(brightnessEvent)
                 .setBrightnessAdjustmentFlag(BrightnessReason.ADJUSTMENT_AUTO)
                 .setShouldUpdateScreenBrightnessSetting(true)
+                .setIsUserInitiatedChange(true)
                 .build();
         DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy
-                .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f));
+                .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f,
+                        /* userSetBrightnessChanged= */ true));
         assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState);
     }
 
@@ -560,9 +562,11 @@
                 .setBrightnessEvent(brightnessEvent)
                 .setBrightnessAdjustmentFlag(BrightnessReason.ADJUSTMENT_AUTO_TEMP)
                 .setShouldUpdateScreenBrightnessSetting(true)
+                .setIsUserInitiatedChange(true)
                 .build();
         DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy
-                .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f));
+                .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f,
+                        /* userSetBrightnessChanged= */ true));
         assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState);
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
index 47f1746..3534325 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
@@ -60,7 +60,8 @@
                         .build();
         DisplayBrightnessState updatedDisplayBrightnessState =
                 mBoostBrightnessStrategy.updateBrightness(
-                        new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+                        new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+                                /* userSetBrightnessChanged= */ false));
         assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
index 9246780..bd6d8be 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
@@ -57,7 +57,8 @@
                         .build();
         DisplayBrightnessState updatedDisplayBrightnessState =
                 mDozeBrightnessModeStrategy.updateBrightness(
-                        new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+                        new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+                                /* userSetBrightnessChanged= */ false));
         assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java
index c4767ae..4cae35d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java
@@ -57,10 +57,12 @@
                         .setSdrBrightness(currentBrightness)
                         .setDisplayBrightnessStrategyName(mFallbackBrightnessStrategy.getName())
                         .setShouldUpdateScreenBrightnessSetting(true)
+                        .setIsUserInitiatedChange(true)
                         .build();
         DisplayBrightnessState updatedDisplayBrightnessState =
                 mFallbackBrightnessStrategy.updateBrightness(
-                        new StrategyExecutionRequest(displayPowerRequest, currentBrightness));
+                        new StrategyExecutionRequest(displayPowerRequest, currentBrightness,
+                                /* userSetBrightnessChanged= */ true));
         assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
index 682c9cc..fdaf8f6 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
@@ -61,7 +61,8 @@
                         .build();
         DisplayBrightnessState updatedDisplayBrightnessState =
                 mFollowerBrightnessStrategy.updateBrightness(
-                        new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+                        new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+                                /* userSetBrightnessChanged= */ false));
         assertEquals(expectedDisplayBrightnessState, updatedDisplayBrightnessState);
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java
index ccf6309..e3c2760 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java
@@ -72,7 +72,8 @@
                         .build();
         DisplayBrightnessState updatedDisplayBrightnessState =
                 mOffloadBrightnessStrategy.updateBrightness(
-                        new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+                        new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+                                /* userSetBrightnessChanged= */ false));
         assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
         assertEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT, mOffloadBrightnessStrategy
                 .getOffloadScreenBrightness(), 0.0f);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
index 8e7b463..ebe407b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
@@ -60,7 +60,8 @@
                         .build();
         DisplayBrightnessState updatedDisplayBrightnessState =
                 mOverrideBrightnessStrategy.updateBrightness(
-                        new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+                        new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+                                /* userSetBrightnessChanged= */ false));
         assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
index e799d0e..4bad569 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
@@ -58,7 +58,8 @@
                         .build();
         DisplayBrightnessState updatedDisplayBrightnessState =
                 mScreenOffBrightnessModeStrategy.updateBrightness(
-                        new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+                        new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+                                /* userSetBrightnessChanged= */ false));
         assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
index aaada41..5410a20 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
@@ -60,7 +60,8 @@
                         .build();
         DisplayBrightnessState updatedDisplayBrightnessState =
                 mTemporaryBrightnessStrategy.updateBrightness(
-                        new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+                        new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+                                /* userSetBrightnessChanged= */ false));
         assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
index cf6146f..34c6ba9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
@@ -117,11 +117,11 @@
             BaseModeRefreshRateVote(60f), SizeVote(1000, 1000, 1000, 1000), RenderVote(60f, 90f)),
         PREFERRED_REFRESH_RATE(false, 0, 60f, 0f, 0f,
             BaseModeRefreshRateVote(60f), SizeVote(1000, 1000, 1000, 1000), null),
-        PREFERRED_REFRESH_RATE_IGNORED(true, 0, 60f, 0f, 0f,
-            null, null, null),
+        PREFERRED_REFRESH_RATE_IGNORE_BASE_MODE_CONVERSION(true, 0, 60f, 0f, 0f,
+            RequestedRefreshRateVote(60f), null, null),
         PREFERRED_REFRESH_RATE_INVALID(false, 0, 25f, 0f, 0f,
             null, null, null),
         SYNTHETIC_MODE(false, 99, 0f, 0f, 0f,
-            RenderVote(45f, 45f), SizeVote(1000, 1000, 1000, 1000), null),
+            RequestedRefreshRateVote(45f), SizeVote(1000, 1000, 1000, 1000), null),
     }
 }
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/RequestedRefreshRateVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/RequestedRefreshRateVoteTest.kt
new file mode 100644
index 0000000..dbe9e4a
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/RequestedRefreshRateVoteTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.mode
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RequestedRefreshRateVoteTest {
+
+    @Test
+    fun `updates requestedRefreshRates`() {
+        val refreshRate = 90f
+        val vote = RequestedRefreshRateVote(refreshRate)
+        val summary = createVotesSummary()
+
+        vote.updateSummary(summary)
+
+        assertThat(summary.requestedRefreshRates).hasSize(1)
+        assertThat(summary.requestedRefreshRates).contains(refreshRate)
+    }
+
+    @Test
+    fun `updates requestedRefreshRates with multiple refresh rates`() {
+        val refreshRate1 = 90f
+        val vote1 = RequestedRefreshRateVote(refreshRate1)
+
+        val refreshRate2 = 60f
+        val vote2 = RequestedRefreshRateVote(refreshRate2)
+
+        val summary = createVotesSummary()
+
+        vote1.updateSummary(summary)
+        vote2.updateSummary(summary)
+
+        assertThat(summary.requestedRefreshRates).hasSize(2)
+        assertThat(summary.requestedRefreshRates).containsExactly(refreshRate1, refreshRate2)
+    }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
index 5da1bb6..dd5e1be 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
@@ -152,13 +152,51 @@
 
         assertThat(result.map { it.modeId }).containsExactlyElementsIn(testCase.expectedModeIds)
     }
+
+    @Test
+    fun `summary invalid if has requestedRefreshRate less than minRenederRate`() {
+        val summary = createSummary()
+        summary.requestedRefreshRates = setOf(30f, 90f)
+        summary.minRenderFrameRate = 60f
+        summary.maxRenderFrameRate = 120f
+
+        val result = summary.filterModes(arrayOf(createMode(1, 90f, 90f)))
+
+        assertThat(result).isEmpty()
+    }
+
+    @Test
+    fun `summary invalid if has requestedRefreshRate more than maxRenderFrameRate`() {
+        val summary = createSummary()
+        summary.requestedRefreshRates = setOf(60f, 240f)
+        summary.minRenderFrameRate = 60f
+        summary.maxRenderFrameRate = 120f
+
+        val result = summary.filterModes(arrayOf(createMode(1, 90f, 90f)))
+
+        assertThat(result).isEmpty()
+    }
+
+    @Test
+    fun `summary valid if all requestedRefreshRates inside render rate limits`() {
+        val summary = createSummary()
+        summary.requestedRefreshRates = setOf(60f, 90f)
+        summary.minRenderFrameRate = 60f
+        summary.maxRenderFrameRate = 120f
+
+        val result = summary.filterModes(arrayOf(createMode(1, 90f, 90f)))
+
+        assertThat(result).hasSize(1)
+    }
 }
+
+
 private fun createMode(modeId: Int, refreshRate: Float, vsyncRate: Float): Display.Mode {
     return Display.Mode(modeId, 600, 800, refreshRate, vsyncRate, false,
             FloatArray(0), IntArray(0))
 }
 
-private fun createSummary(supportedModesVoteEnabled: Boolean): VoteSummary {
+private fun createSummary(supportedModesVoteEnabled: Boolean = false): VoteSummary {
     val summary = createVotesSummary(supportedModesVoteEnabled = supportedModesVoteEnabled)
     summary.width = 600
     summary.height = 800