Merge "Un-deprecate AutomaticZenRule constructor" into main
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp
index 8b8d361..a142450 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.cpp
+++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp
@@ -134,8 +134,9 @@
     return env;
 }
 
-std::unique_ptr<Device> Device::open(int32_t id, const char* name, int32_t vid, int32_t pid,
-                                     uint16_t bus, const std::vector<uint8_t>& descriptor,
+std::unique_ptr<Device> Device::open(int32_t id, const char* name, const char* uniq, int32_t vid,
+                                     int32_t pid, uint16_t bus,
+                                     const std::vector<uint8_t>& descriptor,
                                      std::unique_ptr<DeviceCallback> callback) {
     size_t size = descriptor.size();
     if (size > HID_MAX_DESCRIPTOR_SIZE) {
@@ -152,8 +153,7 @@
     struct uhid_event ev = {};
     ev.type = UHID_CREATE2;
     strlcpy(reinterpret_cast<char*>(ev.u.create2.name), name, sizeof(ev.u.create2.name));
-    std::string uniq = android::base::StringPrintf("Id: %d", id);
-    strlcpy(reinterpret_cast<char*>(ev.u.create2.uniq), uniq.c_str(), sizeof(ev.u.create2.uniq));
+    strlcpy(reinterpret_cast<char*>(ev.u.create2.uniq), uniq, sizeof(ev.u.create2.uniq));
     memcpy(&ev.u.create2.rd_data, descriptor.data(), size * sizeof(ev.u.create2.rd_data[0]));
     ev.u.create2.rd_size = size;
     ev.u.create2.bus = bus;
@@ -314,19 +314,31 @@
     return data;
 }
 
-static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, jint vid,
-                        jint pid, jint bus, jbyteArray rawDescriptor, jobject callback) {
+static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jstring rawUniq, jint id,
+                        jint vid, jint pid, jint bus, jbyteArray rawDescriptor, jobject callback) {
     ScopedUtfChars name(env, rawName);
     if (name.c_str() == nullptr) {
         return 0;
     }
 
+    std::string uniq;
+    if (rawUniq != nullptr) {
+        uniq = ScopedUtfChars(env, rawUniq);
+    } else {
+        uniq = android::base::StringPrintf("Id: %d", id);
+    }
+
+    if (uniq.c_str() == nullptr) {
+        return 0;
+    }
+
     std::vector<uint8_t> desc = getData(env, rawDescriptor);
 
     std::unique_ptr<uhid::DeviceCallback> cb(new uhid::DeviceCallback(env, callback));
 
     std::unique_ptr<uhid::Device> d =
-            uhid::Device::open(id, reinterpret_cast<const char*>(name.c_str()), vid, pid, bus, desc,
+            uhid::Device::open(id, reinterpret_cast<const char*>(name.c_str()),
+                               reinterpret_cast<const char*>(uniq.c_str()), vid, pid, bus, desc,
                                std::move(cb));
     return reinterpret_cast<jlong>(d.release());
 }
@@ -370,7 +382,7 @@
 
 static JNINativeMethod sMethods[] = {
         {"nativeOpenDevice",
-         "(Ljava/lang/String;IIII[B"
+         "(Ljava/lang/String;Ljava/lang/String;IIII[B"
          "Lcom/android/commands/hid/Device$DeviceCallback;)J",
          reinterpret_cast<void*>(openDevice)},
         {"nativeSendReport", "(J[B)V", reinterpret_cast<void*>(sendReport)},
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.h b/cmds/hid/jni/com_android_commands_hid_Device.h
index 9c6060d..bc7a909 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.h
+++ b/cmds/hid/jni/com_android_commands_hid_Device.h
@@ -42,8 +42,9 @@
 
 class Device {
 public:
-    static std::unique_ptr<Device> open(int32_t id, const char* name, int32_t vid, int32_t pid,
-                                        uint16_t bus, const std::vector<uint8_t>& descriptor,
+    static std::unique_ptr<Device> open(int32_t id, const char* name, const char* uniq, int32_t vid,
+                                        int32_t pid, uint16_t bus,
+                                        const std::vector<uint8_t>& descriptor,
                                         std::unique_ptr<DeviceCallback> callback);
 
     ~Device();
diff --git a/cmds/hid/src/com/android/commands/hid/Device.java b/cmds/hid/src/com/android/commands/hid/Device.java
index 0415037..4e8adc3 100644
--- a/cmds/hid/src/com/android/commands/hid/Device.java
+++ b/cmds/hid/src/com/android/commands/hid/Device.java
@@ -71,6 +71,7 @@
 
     private static native long nativeOpenDevice(
             String name,
+            String uniq,
             int id,
             int vid,
             int pid,
@@ -89,6 +90,7 @@
     public Device(
             int id,
             String name,
+            String uniq,
             int vid,
             int pid,
             int bus,
@@ -113,8 +115,9 @@
         } else {
             args.arg1 = id + ":" + vid + ":" + pid;
         }
-        args.arg2 = descriptor;
-        args.arg3 = report;
+        args.arg2 = uniq;
+        args.arg3 = descriptor;
+        args.arg4 = report;
         mHandler.obtainMessage(MSG_OPEN_DEVICE, args).sendToTarget();
         mTimeToSend = SystemClock.uptimeMillis();
     }
@@ -167,11 +170,12 @@
                     mPtr =
                             nativeOpenDevice(
                                     (String) args.arg1,
+                                    (String) args.arg2,
                                     args.argi1,
                                     args.argi2,
                                     args.argi3,
                                     args.argi4,
-                                    (byte[]) args.arg2,
+                                    (byte[]) args.arg3,
                                     new DeviceCallback());
                     pauseEvents();
                     break;
diff --git a/cmds/hid/src/com/android/commands/hid/Event.java b/cmds/hid/src/com/android/commands/hid/Event.java
index 3efb797..3b02279 100644
--- a/cmds/hid/src/com/android/commands/hid/Event.java
+++ b/cmds/hid/src/com/android/commands/hid/Event.java
@@ -56,6 +56,7 @@
     private int mId;
     private String mCommand;
     private String mName;
+    private String mUniq;
     private byte[] mDescriptor;
     private int mVid;
     private int mPid;
@@ -78,6 +79,10 @@
         return mName;
     }
 
+    public String getUniq() {
+        return mUniq;
+    }
+
     public byte[] getDescriptor() {
         return mDescriptor;
     }
@@ -116,8 +121,9 @@
 
     public String toString() {
         return "Event{id=" + mId
-            + ", command=" + String.valueOf(mCommand)
-            + ", name=" + String.valueOf(mName)
+            + ", command=" + mCommand
+            + ", name=" + mName
+            + ", uniq=" + mUniq
             + ", descriptor=" + Arrays.toString(mDescriptor)
             + ", vid=" + mVid
             + ", pid=" + mPid
@@ -149,6 +155,10 @@
             mEvent.mName = name;
         }
 
+        public void setUniq(String uniq) {
+            mEvent.mUniq = uniq;
+        }
+
         public void setDescriptor(byte[] descriptor) {
             mEvent.mDescriptor = descriptor;
         }
@@ -247,6 +257,9 @@
                             case "name":
                                 eb.setName(mReader.nextString());
                                 break;
+                            case "uniq":
+                                eb.setUniq(mReader.nextString());
+                                break;
                             case "vid":
                                 eb.setVid(readInt());
                                 break;
diff --git a/cmds/hid/src/com/android/commands/hid/Hid.java b/cmds/hid/src/com/android/commands/hid/Hid.java
index 2db791fe..5ebfd95 100644
--- a/cmds/hid/src/com/android/commands/hid/Hid.java
+++ b/cmds/hid/src/com/android/commands/hid/Hid.java
@@ -117,8 +117,17 @@
                     "Tried to send command \"" + e.getCommand() + "\" to an unregistered device!");
         }
         int id = e.getId();
-        Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(), e.getBus(),
-                e.getDescriptor(), e.getReport(), e.getFeatureReports(), e.getOutputs());
+        Device d = new Device(
+                id,
+                e.getName(),
+                e.getUniq(),
+                e.getVendorId(),
+                e.getProductId(),
+                e.getBus(),
+                e.getDescriptor(),
+                e.getReport(),
+                e.getFeatureReports(),
+                e.getOutputs());
         mDevices.append(id, d);
     }
 
diff --git a/core/api/current.txt b/core/api/current.txt
index 21d1da5..5772fb4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4445,7 +4445,7 @@
     method public final android.media.session.MediaController getMediaController();
     method @NonNull public android.view.MenuInflater getMenuInflater();
     method @NonNull public android.window.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
-    method public final android.app.Activity getParent();
+    method @Deprecated public final android.app.Activity getParent();
     method @Nullable public android.content.Intent getParentActivityIntent();
     method public android.content.SharedPreferences getPreferences(int);
     method @Nullable public android.net.Uri getReferrer();
@@ -4463,7 +4463,7 @@
     method public void invalidateOptionsMenu();
     method public boolean isActivityTransitionRunning();
     method public boolean isChangingConfigurations();
-    method public final boolean isChild();
+    method @Deprecated public final boolean isChild();
     method public boolean isDestroyed();
     method public boolean isFinishing();
     method public boolean isImmersive();
@@ -10731,7 +10731,6 @@
     field public static final String DROPBOX_SERVICE = "dropbox";
     field public static final String EUICC_SERVICE = "euicc";
     field public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
-    field public static final String FINGERPRINT_SERVICE = "fingerprint";
     field public static final String GAME_SERVICE = "game";
     field public static final String GRAMMATICAL_INFLECTION_SERVICE = "grammatical_inflection";
     field public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
@@ -18051,24 +18050,6 @@
     method public android.graphics.pdf.PdfDocument.PageInfo.Builder setContentRect(android.graphics.Rect);
   }
 
-  public final class PdfRenderer implements java.lang.AutoCloseable {
-    ctor public PdfRenderer(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
-    method public void close();
-    method public int getPageCount();
-    method public android.graphics.pdf.PdfRenderer.Page openPage(int);
-    method public boolean shouldScaleForPrinting();
-  }
-
-  public final class PdfRenderer.Page implements java.lang.AutoCloseable {
-    method public void close();
-    method public int getHeight();
-    method public int getIndex();
-    method public int getWidth();
-    method public void render(@NonNull android.graphics.Bitmap, @Nullable android.graphics.Rect, @Nullable android.graphics.Matrix, int);
-    field public static final int RENDER_MODE_FOR_DISPLAY = 1; // 0x1
-    field public static final int RENDER_MODE_FOR_PRINT = 2; // 0x2
-  }
-
 }
 
 package android.graphics.text {
@@ -20379,54 +20360,6 @@
 
 }
 
-package android.hardware.fingerprint {
-
-  @Deprecated public class FingerprintManager {
-    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.USE_BIOMETRIC, android.Manifest.permission.USE_FINGERPRINT}) public void authenticate(@Nullable android.hardware.fingerprint.FingerprintManager.CryptoObject, @Nullable android.os.CancellationSignal, int, @NonNull android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, @Nullable android.os.Handler);
-    method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean hasEnrolledFingerprints();
-    method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean isHardwareDetected();
-    field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0
-    field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3
-    field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2
-    field public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1
-    field public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5
-    field public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4
-    field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5
-    field public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc
-    field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1
-    field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7
-    field public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9
-    field public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb
-    field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4
-    field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3
-    field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2
-    field public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; // 0xa
-    field public static final int FINGERPRINT_ERROR_VENDOR = 8; // 0x8
-  }
-
-  @Deprecated public abstract static class FingerprintManager.AuthenticationCallback {
-    ctor @Deprecated public FingerprintManager.AuthenticationCallback();
-    method @Deprecated public void onAuthenticationError(int, CharSequence);
-    method @Deprecated public void onAuthenticationFailed();
-    method @Deprecated public void onAuthenticationHelp(int, CharSequence);
-    method @Deprecated public void onAuthenticationSucceeded(android.hardware.fingerprint.FingerprintManager.AuthenticationResult);
-  }
-
-  @Deprecated public static class FingerprintManager.AuthenticationResult {
-    method @Deprecated public android.hardware.fingerprint.FingerprintManager.CryptoObject getCryptoObject();
-  }
-
-  @Deprecated public static final class FingerprintManager.CryptoObject {
-    ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull java.security.Signature);
-    ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher);
-    ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac);
-    method @Deprecated public javax.crypto.Cipher getCipher();
-    method @Deprecated public javax.crypto.Mac getMac();
-    method @Deprecated public java.security.Signature getSignature();
-  }
-
-}
-
 package android.hardware.input {
 
   public final class HostUsiVersion implements android.os.Parcelable {
diff --git a/core/api/removed.txt b/core/api/removed.txt
index 3c7c0d6..c61f163 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -35,6 +35,7 @@
     method @Deprecated @Nullable public String getFeatureId();
     method public abstract android.content.SharedPreferences getSharedPreferences(java.io.File, int);
     method public abstract java.io.File getSharedPreferencesPath(String);
+    field public static final String FINGERPRINT_SERVICE = "fingerprint";
   }
 
   public class ContextWrapper extends android.content.Context {
@@ -145,6 +146,54 @@
 
 }
 
+package android.hardware.fingerprint {
+
+  @Deprecated public class FingerprintManager {
+    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.USE_BIOMETRIC, android.Manifest.permission.USE_FINGERPRINT}) public void authenticate(@Nullable android.hardware.fingerprint.FingerprintManager.CryptoObject, @Nullable android.os.CancellationSignal, int, @NonNull android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, @Nullable android.os.Handler);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean hasEnrolledFingerprints();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean isHardwareDetected();
+    field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0
+    field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3
+    field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2
+    field public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1
+    field public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5
+    field public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4
+    field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5
+    field public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc
+    field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1
+    field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7
+    field public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9
+    field public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb
+    field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4
+    field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3
+    field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2
+    field public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; // 0xa
+    field public static final int FINGERPRINT_ERROR_VENDOR = 8; // 0x8
+  }
+
+  @Deprecated public abstract static class FingerprintManager.AuthenticationCallback {
+    ctor public FingerprintManager.AuthenticationCallback();
+    method public void onAuthenticationError(int, CharSequence);
+    method public void onAuthenticationFailed();
+    method public void onAuthenticationHelp(int, CharSequence);
+    method public void onAuthenticationSucceeded(android.hardware.fingerprint.FingerprintManager.AuthenticationResult);
+  }
+
+  @Deprecated public static class FingerprintManager.AuthenticationResult {
+    method public android.hardware.fingerprint.FingerprintManager.CryptoObject getCryptoObject();
+  }
+
+  @Deprecated public static final class FingerprintManager.CryptoObject {
+    ctor public FingerprintManager.CryptoObject(@NonNull java.security.Signature);
+    ctor public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher);
+    ctor public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac);
+    method public javax.crypto.Cipher getCipher();
+    method public javax.crypto.Mac getMac();
+    method public java.security.Signature getSignature();
+  }
+
+}
+
 package android.media {
 
   public final class AudioFormat implements android.os.Parcelable {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 53d0c03..15f1eb4 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1710,15 +1710,6 @@
 
 }
 
-package android.hardware.fingerprint {
-
-  @Deprecated public class FingerprintManager {
-    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int);
-    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List<android.hardware.biometrics.SensorProperties> getSensorProperties();
-  }
-
-}
-
 package android.hardware.hdmi {
 
   public final class HdmiControlServiceWrapper {
@@ -3930,6 +3921,7 @@
   }
 
   public final class InputMethodInfo implements android.os.Parcelable {
+    ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, boolean, @NonNull String);
     ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, @NonNull String, boolean, @NonNull String);
     ctor @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, @NonNull String, boolean, boolean, @NonNull String);
     ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, int);
diff --git a/core/api/test-removed.txt b/core/api/test-removed.txt
index d802177..2e44176 100644
--- a/core/api/test-removed.txt
+++ b/core/api/test-removed.txt
@@ -1 +1,10 @@
 // Signature format: 2.0
+package android.hardware.fingerprint {
+
+  @Deprecated public class FingerprintManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List<android.hardware.biometrics.SensorProperties> getSensorProperties();
+  }
+
+}
+
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 63cafdc..afbefca 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1126,8 +1126,8 @@
          * @hide
          */
         @Override
-        public void updateStatusBarAppearance(int appearance) {
-            mTaskDescription.setStatusBarAppearance(appearance);
+        public void updateSystemBarsAppearance(int appearance) {
+            mTaskDescription.setSystemBarsAppearance(appearance);
             setTaskDescription(mTaskDescription);
         }
 
@@ -1254,12 +1254,23 @@
         return mApplication;
     }
 
-    /** Is this activity embedded inside of another activity? */
+    /**
+     * Whether this is a child {@link Activity} of an {@link ActivityGroup}.
+     *
+     * @deprecated {@link ActivityGroup} is deprecated.
+     */
+    @Deprecated
     public final boolean isChild() {
         return mParent != null;
     }
 
-    /** Return the parent activity if this view is an embedded child. */
+    /**
+     * Returns the parent {@link Activity} if this is a child {@link Activity} of an
+     * {@link ActivityGroup}.
+     *
+     * @deprecated {@link ActivityGroup} is deprecated.
+     */
+    @Deprecated
     public final Activity getParent() {
         return mParent;
     }
@@ -5535,6 +5546,15 @@
         }
 
         a.recycle();
+        if (first && mTaskDescription.getSystemBarsAppearance() == 0
+                && mWindow != null && mWindow.getSystemBarAppearance() != 0) {
+            // When the theme is applied for the first time during the activity re-creation process,
+            // the attached window restores the system bars appearance from the old window/activity.
+            // Make sure to restore this appearance in TaskDescription too, to prevent the
+            // #setTaskDescription() call below from incorrectly sending an empty value to the
+            // server.
+            mTaskDescription.setSystemBarsAppearance(mWindow.getSystemBarAppearance());
+        }
         setTaskDescription(mTaskDescription);
     }
 
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index f358522..39823a8 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1604,7 +1604,7 @@
         private int mStatusBarColor;
         private int mNavigationBarColor;
         @Appearance
-        private int mStatusBarAppearance;
+        private int mSystemBarsAppearance;
         private boolean mEnsureStatusBarContrastWhenTransparent;
         private boolean mEnsureNavigationBarContrastWhenTransparent;
         private int mResizeMode;
@@ -1804,7 +1804,7 @@
         public TaskDescription(@Nullable String label, @Nullable Icon icon,
                 int colorPrimary, int colorBackground,
                 int statusBarColor, int navigationBarColor,
-                @Appearance int statusBarAppearance,
+                @Appearance int systemBarsAppearance,
                 boolean ensureStatusBarContrastWhenTransparent,
                 boolean ensureNavigationBarContrastWhenTransparent, int resizeMode, int minWidth,
                 int minHeight, int colorBackgroundFloating) {
@@ -1814,7 +1814,7 @@
             mColorBackground = colorBackground;
             mStatusBarColor = statusBarColor;
             mNavigationBarColor = navigationBarColor;
-            mStatusBarAppearance = statusBarAppearance;
+            mSystemBarsAppearance = systemBarsAppearance;
             mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
             mEnsureNavigationBarContrastWhenTransparent =
                     ensureNavigationBarContrastWhenTransparent;
@@ -1843,7 +1843,7 @@
             mColorBackground = other.mColorBackground;
             mStatusBarColor = other.mStatusBarColor;
             mNavigationBarColor = other.mNavigationBarColor;
-            mStatusBarAppearance = other.mStatusBarAppearance;
+            mSystemBarsAppearance = other.mSystemBarsAppearance;
             mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
             mEnsureNavigationBarContrastWhenTransparent =
                     other.mEnsureNavigationBarContrastWhenTransparent;
@@ -1873,8 +1873,8 @@
             if (other.mNavigationBarColor != 0) {
                 mNavigationBarColor = other.mNavigationBarColor;
             }
-            if (other.mStatusBarAppearance != 0) {
-                mStatusBarAppearance = other.mStatusBarAppearance;
+            if (other.mSystemBarsAppearance != 0) {
+                mSystemBarsAppearance = other.mSystemBarsAppearance;
             }
 
             mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
@@ -2148,8 +2148,8 @@
          * @hide
          */
         @Appearance
-        public int getStatusBarAppearance() {
-            return mStatusBarAppearance;
+        public int getSystemBarsAppearance() {
+            return mSystemBarsAppearance;
         }
 
         /**
@@ -2163,8 +2163,8 @@
         /**
          * @hide
          */
-        public void setStatusBarAppearance(@Appearance int statusBarAppearance) {
-            mStatusBarAppearance = statusBarAppearance;
+        public void setSystemBarsAppearance(@Appearance int systemBarsAppearance) {
+            mSystemBarsAppearance = systemBarsAppearance;
         }
 
         /**
@@ -2291,7 +2291,7 @@
             dest.writeInt(mColorBackground);
             dest.writeInt(mStatusBarColor);
             dest.writeInt(mNavigationBarColor);
-            dest.writeInt(mStatusBarAppearance);
+            dest.writeInt(mSystemBarsAppearance);
             dest.writeBoolean(mEnsureStatusBarContrastWhenTransparent);
             dest.writeBoolean(mEnsureNavigationBarContrastWhenTransparent);
             dest.writeInt(mResizeMode);
@@ -2315,7 +2315,7 @@
             mColorBackground = source.readInt();
             mStatusBarColor = source.readInt();
             mNavigationBarColor = source.readInt();
-            mStatusBarAppearance = source.readInt();
+            mSystemBarsAppearance = source.readInt();
             mEnsureStatusBarContrastWhenTransparent = source.readBoolean();
             mEnsureNavigationBarContrastWhenTransparent = source.readBoolean();
             mResizeMode = source.readInt();
@@ -2347,7 +2347,8 @@
                             ? " (contrast when transparent)" : "")
                     + " resizeMode: " + ActivityInfo.resizeModeToString(mResizeMode)
                     + " minWidth: " + mMinWidth + " minHeight: " + mMinHeight
-                    + " colorBackgrounFloating: " + mColorBackgroundFloating;
+                    + " colorBackgrounFloating: " + mColorBackgroundFloating
+                    + " systemBarsAppearance: " + mSystemBarsAppearance;
         }
 
         @Override
@@ -2367,7 +2368,7 @@
             result = result * 31 + mColorBackgroundFloating;
             result = result * 31 + mStatusBarColor;
             result = result * 31 + mNavigationBarColor;
-            result = result * 31 + mStatusBarAppearance;
+            result = result * 31 + mSystemBarsAppearance;
             result = result * 31 + (mEnsureStatusBarContrastWhenTransparent ? 1 : 0);
             result = result * 31 + (mEnsureNavigationBarContrastWhenTransparent ? 1 : 0);
             result = result * 31 + mResizeMode;
@@ -2390,7 +2391,7 @@
                     && mColorBackground == other.mColorBackground
                     && mStatusBarColor == other.mStatusBarColor
                     && mNavigationBarColor == other.mNavigationBarColor
-                    && mStatusBarAppearance == other.mStatusBarAppearance
+                    && mSystemBarsAppearance == other.mSystemBarsAppearance
                     && mEnsureStatusBarContrastWhenTransparent
                             == other.mEnsureStatusBarContrastWhenTransparent
                     && mEnsureNavigationBarContrastWhenTransparent
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index ab5395e..9f2e473 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -562,6 +562,12 @@
      * audio attributes. Notification channels with an {@link #getImportance() importance} of at
      * least {@link NotificationManager#IMPORTANCE_DEFAULT} should have a sound.
      *
+     * Note: An app-specific sound can be provided in the Uri parameter, but because channels are
+     * persistent for the duration of the app install, and are backed up and restored, the Uri
+     * should be stable. For this reason it is not recommended to use a
+     * {@link ContentResolver#SCHEME_ANDROID_RESOURCE} uri, as resource ids can change on app
+     * upgrade.
+     *
      * Only modifiable before the channel is submitted to
      * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
      */
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index c7e5d88..7f2ec53 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5067,6 +5067,7 @@
      * {@link android.hardware.fingerprint.FingerprintManager} for handling management
      * of fingerprints.
      *
+     * @removed See {@link android.hardware.biometrics.BiometricPrompt}
      * @see #getSystemService(String)
      * @see android.hardware.fingerprint.FingerprintManager
      */
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 42dd87a..443aadd 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -8183,7 +8183,7 @@
 
                 // launch flags
                 else if (uri.startsWith("launchFlags=", i)) {
-                    intent.mFlags = Integer.decode(value);
+                    intent.mFlags = decodeInteger(value);
                     if ((flags& URI_ALLOW_UNSAFE) == 0) {
                         intent.mFlags &= ~IMMUTABLE_FLAGS;
                     }
@@ -8191,7 +8191,7 @@
 
                 // extended flags
                 else if (uri.startsWith("extendedLaunchFlags=", i)) {
-                    intent.mExtendedFlags = Integer.decode(value);
+                    intent.mExtendedFlags = decodeInteger(value);
                 }
 
                 // package
@@ -8401,7 +8401,7 @@
                 isIntentFragment = true;
                 i += 12;
                 int j = uri.indexOf(')', i);
-                intent.mFlags = Integer.decode(uri.substring(i, j));
+                intent.mFlags = decodeInteger(uri.substring(i, j));
                 if ((flags& URI_ALLOW_UNSAFE) == 0) {
                     intent.mFlags &= ~IMMUTABLE_FLAGS;
                 }
@@ -8512,6 +8512,23 @@
         return intent;
     }
 
+    private static Integer decodeInteger(String value) {
+        try {
+            return Integer.decode(value);
+        } catch (NumberFormatException e) {
+            try {
+                if (value != null && value.startsWith("0x")) {
+                    // In toUriInner, we do "0x".append(Integer.toHexString).
+                    // Sometimes "decode" fails to parse, e.g. 0x90000000.
+                    return Integer.parseUnsignedInt(value.substring(2), 16);
+                }
+            } catch (NumberFormatException ignored) {
+                // ignored, throw the original exception
+            }
+            throw e;
+        }
+    }
+
     /** @hide */
     public interface CommandOptionHandler {
         boolean handleOption(String opt, ShellCommand cmd);
@@ -8577,7 +8594,7 @@
                 case "--ei": {
                     String key = cmd.getNextArgRequired();
                     String value = cmd.getNextArgRequired();
-                    intent.putExtra(key, Integer.decode(value));
+                    intent.putExtra(key, decodeInteger(value));
                 }
                 break;
                 case "--eu": {
@@ -8601,7 +8618,7 @@
                     String[] strings = value.split(",");
                     int[] list = new int[strings.length];
                     for (int i = 0; i < strings.length; i++) {
-                        list[i] = Integer.decode(strings[i]);
+                        list[i] = decodeInteger(strings[i]);
                     }
                     intent.putExtra(key, list);
                 }
@@ -8612,7 +8629,7 @@
                     String[] strings = value.split(",");
                     ArrayList<Integer> list = new ArrayList<>(strings.length);
                     for (int i = 0; i < strings.length; i++) {
-                        list.add(Integer.decode(strings[i]));
+                        list.add(decodeInteger(strings[i]));
                     }
                     intent.putExtra(key, list);
                 }
@@ -8747,7 +8764,7 @@
                         arg = false;
                     } else {
                         try {
-                            arg = Integer.decode(value) != 0;
+                            arg = decodeInteger(value) != 0;
                         } catch (NumberFormatException ex) {
                             throw new IllegalArgumentException("Invalid boolean value: " + value);
                         }
@@ -8777,7 +8794,7 @@
                 break;
                 case "-f":
                     String str = cmd.getNextArgRequired();
-                    intent.setFlags(Integer.decode(str).intValue());
+                    intent.setFlags(decodeInteger(str).intValue());
                     break;
                 case "--grant-read-uri-permission":
                     intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 6bb9c33..41c1f17 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -697,8 +697,9 @@
     public List<UserHandle> getProfiles() {
         if (mUserManager.isManagedProfile()
                 || (android.multiuser.Flags.enableLauncherAppsHiddenProfileChecks()
-                        && android.os.Flags.allowPrivateProfile()
-                        && mUserManager.isPrivateProfile())) {
+                    && android.os.Flags.allowPrivateProfile()
+                    && android.multiuser.Flags.enablePrivateSpaceFeatures()
+                    && mUserManager.isPrivateProfile())) {
             // If it's a managed or private profile, only return the current profile.
             final List result = new ArrayList(1);
             result.add(android.os.Process.myUserHandle());
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index ac80561..48a7cc9 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -184,3 +184,11 @@
     description: "Enable Private Space telephony and SMS intent redirection to the main user"
     bug: "325576602"
 }
+
+flag {
+    name: "block_private_space_creation"
+    namespace: "profile_experiences"
+    description: "Allow blocking private space creation based on specific conditions"
+    bug: "290333800"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 6b2814e..58aafbc 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -778,6 +778,16 @@
          */
         float[] getAutoBrightnessLuxLevels(int mode);
 
+        /**
+         * @return The current brightness setting
+         */
+        float getBrightness();
+
+        /**
+         * @return The brightness value that is used when the device is in doze
+         */
+        float getDozeBrightness();
+
         /** Returns whether displayoffload supports the given display state. */
         static boolean isSupportedOffloadState(int displayState) {
             return Display.isSuspendedState(displayState);
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 81e321d..b0f69f5 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -83,7 +83,8 @@
 
 /**
  * A class that coordinates access to the fingerprint hardware.
- * @deprecated See {@link BiometricPrompt} which shows a system-provided dialog upon starting
+ *
+ * @removed See {@link BiometricPrompt} which shows a system-provided dialog upon starting
  * authentication. In a world where devices may have different types of biometric authentication,
  * it's much more realistic to have a system-provided authentication dialog since the method may
  * vary by vendor/device.
@@ -94,7 +95,6 @@
 @RequiresFeature(PackageManager.FEATURE_FINGERPRINT)
 public class FingerprintManager implements BiometricAuthenticator, BiometricFingerprintConstants {
     private static final String TAG = "FingerprintManager";
-    private static final boolean DEBUG = true;
     private static final int MSG_ENROLL_RESULT = 100;
     private static final int MSG_ACQUIRED = 101;
     private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
@@ -196,6 +196,7 @@
 
     /**
      * Retrieves a test session for FingerprintManager.
+     *
      * @hide
      */
     @TestApi
@@ -254,9 +255,10 @@
     }
 
     /**
-     * A wrapper class for the crypto objects supported by FingerprintManager. Currently the
+     * A wrapper class for the crypto objects supported by FingerprintManager. Currently, the
      * framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects.
-     * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}
+     *
+     * @removed See {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}
      */
     @Deprecated
     public static final class CryptoObject extends android.hardware.biometrics.CryptoObject {
@@ -330,7 +332,8 @@
     /**
      * Container for callback data from {@link FingerprintManager#authenticate(CryptoObject,
      *     CancellationSignal, int, AuthenticationCallback, Handler)}.
-     * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult}
+     *
+     * @removed See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult}
      */
     @Deprecated
     public static class AuthenticationResult {
@@ -392,7 +395,8 @@
      * FingerprintManager#authenticate(CryptoObject, CancellationSignal,
      * int, AuthenticationCallback, Handler) } must provide an implementation of this for listening to
      * fingerprint events.
-     * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}
+     *
+     * @removed See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}
      */
     @Deprecated
     public static abstract class AuthenticationCallback
@@ -455,6 +459,7 @@
     /**
      * Callback structure provided for {@link #detectFingerprint(CancellationSignal,
      * FingerprintDetectionCallback, int, Surface)}.
+     *
      * @hide
      */
     public interface FingerprintDetectionCallback {
@@ -608,7 +613,8 @@
      *         by <a href="{@docRoot}training/articles/keystore.html">Android Keystore
      *         facility</a>.
      * @throws IllegalStateException if the crypto primitive is not initialized.
-     * @deprecated See {@link BiometricPrompt#authenticate(CancellationSignal, Executor,
+     *
+     * @removed See {@link BiometricPrompt#authenticate(CancellationSignal, Executor,
      * BiometricPrompt.AuthenticationCallback)} and {@link BiometricPrompt#authenticate(
      * BiometricPrompt.CryptoObject, CancellationSignal, Executor,
      * BiometricPrompt.AuthenticationCallback)}
@@ -623,6 +629,7 @@
     /**
      * Per-user version of authenticate.
      * @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FingerprintAuthenticateOptions)}.
+     *
      * @hide
      */
     @Deprecated
@@ -635,6 +642,7 @@
     /**
      * Per-user and per-sensor version of authenticate.
      * @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FingerprintAuthenticateOptions)}.
+     *
      * @hide
      */
     @Deprecated
@@ -651,6 +659,7 @@
 
     /**
      * Version of authenticate with additional options.
+     *
      * @hide
      */
     @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT})
@@ -698,6 +707,7 @@
     /**
      * Uses the fingerprint hardware to detect for the presence of a finger, without giving details
      * about accept/reject/lockout.
+     *
      * @hide
      */
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -740,6 +750,7 @@
      * @param callback an object to receive enrollment events
      * @param shouldLogMetrics a flag that indicates if enrollment failure/success metrics
      * should be logged.
+     *
      * @hide
      */
     @RequiresPermission(MANAGE_FINGERPRINT)
@@ -810,6 +821,7 @@
     /**
      * Same as {@link #generateChallenge(int, GenerateChallengeCallback)}, but assumes the first
      * enumerated sensor.
+     *
      * @hide
      */
     @RequiresPermission(MANAGE_FINGERPRINT)
@@ -824,6 +836,7 @@
 
     /**
      * Revokes the specified challenge.
+     *
      * @hide
      */
     @RequiresPermission(MANAGE_FINGERPRINT)
@@ -849,6 +862,7 @@
      * @param sensorId Sensor ID that this operation takes effect for
      * @param userId User ID that this operation takes effect for.
      * @param hardwareAuthToken An opaque token returned by password confirmation.
+     *
      * @hide
      */
     @RequiresPermission(RESET_FINGERPRINT_LOCKOUT)
@@ -886,6 +900,7 @@
 
     /**
      * Removes all fingerprint templates for the given user.
+     *
      * @hide
      */
     @RequiresPermission(MANAGE_FINGERPRINT)
@@ -1005,6 +1020,7 @@
     /**
      * Forwards BiometricStateListener to FingerprintService
      * @param listener new BiometricStateListener being added
+     *
      * @hide
      */
     public void registerBiometricStateListener(@NonNull BiometricStateListener listener) {
@@ -1156,7 +1172,8 @@
     }
 
     /**
-     * This is triggered by SideFpsEventHandler
+     * This is triggered by SideFpsEventHandler.
+     *
      * @hide
      */
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -1169,7 +1186,8 @@
      * Determine if there is at least one fingerprint enrolled.
      *
      * @return true if at least one fingerprint is enrolled, false otherwise
-     * @deprecated See {@link BiometricPrompt} and
+     *
+     * @removed See {@link BiometricPrompt} and
      * {@link FingerprintManager#FINGERPRINT_ERROR_NO_FINGERPRINTS}
      */
     @Deprecated
@@ -1203,7 +1221,8 @@
      * Determine if fingerprint hardware is present and functional.
      *
      * @return true if hardware is present and functional, false otherwise.
-     * @deprecated See {@link BiometricPrompt} and
+     *
+     * @removed See {@link BiometricPrompt} and
      * {@link FingerprintManager#FINGERPRINT_ERROR_HW_UNAVAILABLE}
      */
     @Deprecated
@@ -1229,6 +1248,7 @@
 
     /**
      * Get statically configured sensor properties.
+     *
      * @hide
      */
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -1247,6 +1267,7 @@
     /**
      * Returns whether the device has a power button fingerprint sensor.
      * @return boolean indicating whether power button is fingerprint sensor
+     *
      * @hide
      */
     public boolean isPowerbuttonFps() {
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 800ba6d..23d6007 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -85,6 +85,7 @@
     boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable, int mUserId);
     boolean isRestricted(int userId);
     boolean canHaveRestrictedProfile(int userId);
+    boolean canAddPrivateProfile(int userId);
     int getUserSerialNumber(int userId);
     int getUserHandle(int userSerialNumber);
     int getUserRestrictionSource(String restrictionKey, int userId);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 9757a10..fdaa0b4 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2353,6 +2353,17 @@
     public static final int USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS = 7;
 
     /**
+     * Indicates user operation failed because user is disabled on the device.
+     * @hide
+     */
+    public static final int USER_OPERATION_ERROR_DISABLED_USER = 8;
+    /**
+     * Indicates user operation failed because user is disabled on the device.
+     * @hide
+     */
+    public static final int USER_OPERATION_ERROR_PRIVATE_PROFILE = 9;
+
+    /**
      * Result returned from various user operations.
      *
      * @hide
@@ -2366,7 +2377,9 @@
             USER_OPERATION_ERROR_CURRENT_USER,
             USER_OPERATION_ERROR_LOW_STORAGE,
             USER_OPERATION_ERROR_MAX_USERS,
-            USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS
+            USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS,
+            USER_OPERATION_ERROR_DISABLED_USER,
+            USER_OPERATION_ERROR_PRIVATE_PROFILE,
     })
     public @interface UserOperationResult {}
 
@@ -2563,6 +2576,17 @@
     }
 
     /**
+     * Returns whether the device supports Private Profile
+     * @hide
+     */
+    public static boolean isPrivateProfileEnabled() {
+        if (android.multiuser.Flags.blockPrivateSpaceCreation()) {
+            return !ActivityManager.isLowRamDeviceStatic();
+        }
+        return true;
+    }
+
+    /**
      * Returns whether multiple admins are enabled on the device
      * @hide
      */
@@ -3155,6 +3179,27 @@
     }
 
     /**
+     * Checks if it's possible to add a private profile to the context user
+     * @return whether the context user can add a private profile.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS},
+            conditional = true)
+    @UserHandleAware
+    public boolean canAddPrivateProfile() {
+        if (android.multiuser.Flags.blockPrivateSpaceCreation()) {
+            try {
+                return mService.canAddPrivateProfile(mUserId);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+        }
+        return true;
+    }
+
+    /**
      * Returns whether the context user has at least one restricted profile associated with it.
      * @return whether the user has a restricted profile associated with it
      * @hide
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index 6140812..df4a2bb 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -662,6 +662,8 @@
 
     /**
      * Id of the current speaker
+     *
+     * <p>Only values between 0 and {@link #getMaxSpeakerId} (inclusive) are accepted.
      */
     @DataClass.Generated.Member
     @FlaggedApi(Flags.FLAG_ALLOW_SPEAKER_ID_EGRESS)
@@ -982,6 +984,8 @@
 
         /**
          * Id of the current speaker
+         *
+         * <p>Only values between 0 and {@link #getMaxSpeakerId} (inclusive) are accepted.
          */
         @DataClass.Generated.Member
         @FlaggedApi(Flags.FLAG_ALLOW_SPEAKER_ID_EGRESS)
@@ -1228,7 +1232,7 @@
     }
 
     @DataClass.Generated(
-            time = 1704944087827L,
+            time = 1709773165191L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
             inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\npublic static final  int BACKGROUND_AUDIO_POWER_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final  java.lang.String EXTRA_PROXIMITY\npublic static final  int PROXIMITY_UNKNOWN\npublic static final  int PROXIMITY_NEAR\npublic static final  int PROXIMITY_FAR\nprivate final  int mSpeakerId\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate final  int mBackgroundAudioPower\nprivate static  int defaultSpeakerId()\npublic static @android.annotation.FlaggedApi int getMaxSpeakerId()\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\nprivate static  int defaultBackgroundAudioPower()\npublic static  int getMaxBackgroundAudioPower()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic  void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic  android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index b91a878..4d4cb6c 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -194,13 +194,6 @@
     public static final String SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION =
             "settings_remote_device_credential_validation";
 
-    /**
-     * Flag to enable/disable to start treating any calls to "suspend" an app as "quarantine".
-     * @hide
-     */
-    public static final String SETTINGS_TREAT_PAUSE_AS_QUARANTINE =
-            "settings_treat_pause_as_quarantine";
-
     private static final Map<String, String> DEFAULT_FLAGS;
 
     static {
@@ -246,7 +239,6 @@
         DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false");
         // TODO: b/298454866 Replace with Trunk Stable Feature Flag
         DEFAULT_FLAGS.put(SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS, "false");
-        DEFAULT_FLAGS.put(SETTINGS_TREAT_PAUSE_AS_QUARANTINE, "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
@@ -264,7 +256,6 @@
         PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA);
         PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA_PHASE2);
         PERSISTENT_FLAGS.add(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM);
-        PERSISTENT_FLAGS.add(SETTINGS_TREAT_PAUSE_AS_QUARANTINE);
     }
 
     /**
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 124aece..c66abe8 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -889,11 +889,11 @@
      * @hide
      */
     @Override
-    protected int calculateFrameRateCategory(float sizePercentage) {
+    protected int calculateFrameRateCategory(int width, int height) {
         if (mMinusTwoFrameIntervalMillis > 15 && mMinusOneFrameIntervalMillis > 15) {
             return FRAME_RATE_CATEGORY_NORMAL;
         }
-        return super.calculateFrameRateCategory(sizePercentage);
+        return super.calculateFrameRateCategory(width, height);
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a6f380d..f0bde97 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2371,6 +2371,39 @@
      */
     protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
 
+    /**
+     * This indicates that the frame rate category was chosen because it was a small area update.
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_REASON_SMALL = 0x0100_0000;
+
+    /**
+     * This indicates that the frame rate category was chosen because it was an intermittent update.
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_REASON_INTERMITTENT = 0x0200_0000;
+
+    /**
+     * This indicates that the frame rate category was chosen because it was a large View.
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_REASON_LARGE = 0x03000000;
+
+    /**
+     * This indicates that the frame rate category was chosen because it was requested.
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_REASON_REQUESTED = 0x0400_0000;
+
+    /**
+     * This indicates that the frame rate category was chosen because an invalid frame rate was
+     * requested.
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_REASON_INVALID = 0x0500_0000;
+
+    private static final int FRAME_RATE_CATEGORY_REASON_MASK = 0xFFFF_0000;
+
     private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
     private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
     // Used to set frame rate compatibility.
@@ -5637,10 +5670,17 @@
     private ViewTranslationResponse mViewTranslationResponse;
 
     /**
-     * A threshold value to determine the frame rate category of the View based on the size.
+     * Threshold size for something to be considered a small area update (in DP).
+     * This is the dimension for both width and height.
      */
-    private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
+    private static final float FRAME_RATE_SMALL_SIZE_THRESHOLD = 40f;
 
+    /**
+     * Threshold size for something to be considered a small area update (in DP) if
+     * it is narrow. This is for either width OR height. For example, a narrow progress
+     * bar could be considered a small area.
+     */
+    private static final float FRAME_RATE_NARROW_THRESHOLD = 10f;
 
     private static final long INFREQUENT_UPDATE_INTERVAL_MILLIS = 100;
     private static final int INFREQUENT_UPDATE_COUNTS = 2;
@@ -33655,18 +33695,28 @@
      *
      * @hide
      */
-    protected int calculateFrameRateCategory(float sizePercentage) {
+    protected int calculateFrameRateCategory(int width, int height) {
         if (mMinusTwoFrameIntervalMillis + mMinusOneFrameIntervalMillis
                 < INFREQUENT_UPDATE_INTERVAL_MILLIS) {
-            if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
-                return FRAME_RATE_CATEGORY_NORMAL;
+            DisplayMetrics displayMetrics = mResources.getDisplayMetrics();
+            float density = displayMetrics.density;
+            if (density == 0f) {
+                density = 1f;
+            }
+            float widthDp = width / density;
+            float heightDp = height / density;
+            if (widthDp <= FRAME_RATE_NARROW_THRESHOLD
+                    || heightDp <= FRAME_RATE_NARROW_THRESHOLD
+                    || (widthDp <= FRAME_RATE_SMALL_SIZE_THRESHOLD
+                    && heightDp <= FRAME_RATE_SMALL_SIZE_THRESHOLD)) {
+                return FRAME_RATE_CATEGORY_NORMAL | FRAME_RATE_CATEGORY_REASON_SMALL;
             } else {
-                return FRAME_RATE_CATEGORY_HIGH;
+                return FRAME_RATE_CATEGORY_HIGH | FRAME_RATE_CATEGORY_REASON_LARGE;
             }
         }
 
         if (mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS) {
-            return FRAME_RATE_CATEGORY_NORMAL;
+            return FRAME_RATE_CATEGORY_NORMAL | FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
         }
         return mLastFrameRateCategory;
     }
@@ -33674,12 +33724,9 @@
     private void votePreferredFrameRate() {
         // use toolkitSetFrameRate flag to gate the change
         ViewRootImpl viewRootImpl = getViewRootImpl();
-        float sizePercentage = getSizePercentage();
-        int frameRateCateogry = calculateFrameRateCategory(sizePercentage);
-        if (viewRootImpl != null && sizePercentage > 0) {
-            if (sToolkitMetricsForFrameRateDecisionFlagValue) {
-                viewRootImpl.recordViewPercentage(sizePercentage);
-            }
+        int width = mRight - mLeft;
+        int height = mBottom - mTop;
+        if (viewRootImpl != null && (width != 0 && height != 0)) {
             if (viewVelocityApi()) {
                 float velocity = mFrameContentVelocity;
                 if (velocity < 0f) {
@@ -33691,25 +33738,40 @@
                     return;
                 }
             }
-            if (!Float.isNaN(mPreferredFrameRate)) {
-                if (mPreferredFrameRate < 0) {
-                    if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
-                        frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
-                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
-                        frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
-                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
-                        frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
-                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
-                        frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
-                    }
+            int frameRateCategory;
+            if (Float.isNaN(mPreferredFrameRate)) {
+                frameRateCategory = calculateFrameRateCategory(width, height);
+            } else if (mPreferredFrameRate < 0) {
+                if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
+                    frameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE
+                            | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
+                    frameRateCategory = FRAME_RATE_CATEGORY_LOW
+                            | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
+                    frameRateCategory = FRAME_RATE_CATEGORY_NORMAL
+                            | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
+                    frameRateCategory = FRAME_RATE_CATEGORY_HIGH
+                            | FRAME_RATE_CATEGORY_REASON_REQUESTED;
                 } else {
-                    viewRootImpl.votePreferredFrameRate(mPreferredFrameRate,
-                            mFrameRateCompatibility);
-                    return;
+                    // invalid frame rate, default to HIGH
+                    frameRateCategory = FRAME_RATE_CATEGORY_HIGH
+                            | FRAME_RATE_CATEGORY_REASON_INVALID;
                 }
+            } else {
+                viewRootImpl.votePreferredFrameRate(mPreferredFrameRate,
+                        mFrameRateCompatibility);
+                return;
             }
-            viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
-            mLastFrameRateCategory = frameRateCateogry;
+
+            int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK;
+            if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+                int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK;
+                viewRootImpl.recordCategory(category, reason, this);
+            }
+            viewRootImpl.votePreferredFrameRateCategory(category);
+            mLastFrameRateCategory = frameRateCategory;
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 42f6405..0bc9197 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -32,6 +32,11 @@
 import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
 import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
 import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_INVALID;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_LARGE;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_REQUESTED;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_SMALL;
 import static android.view.View.PFLAG_DRAW_ANIMATION;
 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
 import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -841,8 +846,9 @@
     private boolean mInsetsAnimationRunning;
 
     private long mPreviousFrameDrawnTime = -1;
-    // The largest view size percentage to the display size. Used on trace to collect metric.
-    private float mLargestChildPercentage = 0.0f;
+    // The reason the category was changed.
+    private int mFrameRateCategoryChangeReason = 0;
+    private String mFrameRateCategoryView;
 
     /**
      * The resolved pointer icon type requested by this window.
@@ -4847,10 +4853,6 @@
         long fps = NANOS_PER_SEC / timeDiff;
         Trace.setCounter(mFpsTraceName, fps);
         mPreviousFrameDrawnTime = expectedDrawnTime;
-
-        long percentage = (long) (mLargestChildPercentage * 100);
-        Trace.setCounter(mLargestViewTraceName, percentage);
-        mLargestChildPercentage = 0.0f;
     }
 
     private void reportDrawFinished(@Nullable Transaction t, int seqId) {
@@ -12360,16 +12362,29 @@
         // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction.
         // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction
         // (e.g., Window Initialization).
-        if (mIsFrameRateBoosting || mInsetsAnimationRunning) {
+        if (mIsFrameRateBoosting || mInsetsAnimationRunning
+                || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
+                        && mPreferredFrameRate > 0)) {
             frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
         }
 
         try {
             if (mLastPreferredFrameRateCategory != frameRateCategory) {
                 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                    String reason = "none";
+                    switch (mFrameRateCategoryChangeReason) {
+                        case FRAME_RATE_CATEGORY_REASON_INTERMITTENT -> reason = "intermittent";
+                        case FRAME_RATE_CATEGORY_REASON_SMALL -> reason = "small";
+                        case FRAME_RATE_CATEGORY_REASON_LARGE -> reason = "large";
+                        case FRAME_RATE_CATEGORY_REASON_REQUESTED -> reason = "requested";
+                        case FRAME_RATE_CATEGORY_REASON_INVALID -> reason = "invalid frame rate";
+                    }
+                    String sourceView = mFrameRateCategoryView == null ? "No View Given"
+                            : mFrameRateCategoryView;
                     Trace.traceBegin(
                             Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRateCategory "
-                                + frameRateCategory);
+                                    + frameRateCategory + ", reason " + reason + ", "
+                                    + sourceView);
                 }
                 mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
                         frameRateCategory, false).applyAsyncUnsafe();
@@ -12383,7 +12398,8 @@
     }
 
     private void setPreferredFrameRate(float preferredFrameRate) {
-        if (!shouldSetFrameRate()) {
+        if (!shouldSetFrameRate() || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
+                && preferredFrameRate > 0)) {
             return;
         }
 
@@ -12543,6 +12559,14 @@
     }
 
     /**
+     * Get the value of mLastPreferredFrameRate
+     */
+    @VisibleForTesting
+    public float getLastPreferredFrameRate() {
+        return mLastPreferredFrameRate;
+    }
+
+    /**
      * Returns whether touch boost is currently enabled.
      */
     @VisibleForTesting
@@ -12592,10 +12616,12 @@
         mWindowlessBackKeyCallback = callback;
     }
 
-    void recordViewPercentage(float percentage) {
+    void recordCategory(int category, int reason, View view) {
         if (!Trace.isEnabled()) return;
-        // Record the largest view of percentage to the display size.
-        mLargestChildPercentage = Math.max(percentage, mLargestChildPercentage);
+        if (category > mPreferredFrameRateCategory) {
+            mFrameRateCategoryChangeReason = reason;
+            mFrameRateCategoryView = view.getClass().getSimpleName();
+        }
     }
 
     /**
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 6b427fc..51229a7 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -666,7 +666,7 @@
          * Update the status bar appearance.
          */
 
-        void updateStatusBarAppearance(int appearance);
+        void updateSystemBarsAppearance(int appearance);
 
         /**
          * Update the navigation bar color to a forced one.
@@ -1038,6 +1038,11 @@
     }
 
     /** @hide */
+    public final void setSystemBarAppearance(@WindowInsetsController.Appearance int appearance) {
+        mSystemBarAppearance = appearance;
+    }
+
+    /** @hide */
     @WindowInsetsController.Appearance
     public final int getSystemBarAppearance() {
         return mSystemBarAppearance;
@@ -1046,12 +1051,12 @@
     /** @hide */
     public final void dispatchOnSystemBarAppearanceChanged(
             @WindowInsetsController.Appearance int appearance) {
-        mSystemBarAppearance = appearance;
+        setSystemBarAppearance(appearance);
         if (mDecorCallback != null) {
             mDecorCallback.onSystemBarAppearanceChanged(appearance);
         }
         if (mWindowControllerCallback != null) {
-            mWindowControllerCallback.updateStatusBarAppearance(appearance);
+            mWindowControllerCallback.updateSystemBarsAppearance(appearance);
         }
     }
 
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 16fecc1..8ddc178 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -499,6 +499,25 @@
     @TestApi
     public InputMethodInfo(@NonNull String packageName, @NonNull String className,
             @NonNull CharSequence label, @NonNull String settingsActivity,
+            boolean supportStylusHandwriting,
+            @NonNull String stylusHandwritingSettingsActivityAttr) {
+        this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
+                settingsActivity, null /* languageSettingsActivity */,
+                null /* subtypes */, 0 /* isDefaultResId */,
+                false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
+                false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
+                false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */,
+                supportStylusHandwriting, false /* supportConnectionlessStylusHandwriting */,
+                stylusHandwritingSettingsActivityAttr, false /* inlineSuggestionsEnabled */);
+    }
+
+    /**
+     * Test API for creating a built-in input method to verify stylus handwriting.
+     * @hide
+     */
+    @TestApi
+    public InputMethodInfo(@NonNull String packageName, @NonNull String className,
+            @NonNull CharSequence label, @NonNull String settingsActivity,
             @NonNull String languageSettingsActivity, boolean supportStylusHandwriting,
             @NonNull String stylusHandwritingSettingsActivityAttr) {
         this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 7dcbbea..78f06b6 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -2655,7 +2655,8 @@
 
     private boolean privateSpaceEnabled() {
         return mIsIntentPicker && android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.allowResolverSheetForPrivateSpace();
+                && android.multiuser.Flags.allowResolverSheetForPrivateSpace()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures();
     }
 
     /**
diff --git a/core/java/com/android/internal/app/SetScreenLockDialogActivity.java b/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
index 93fe37c..360fcaf 100644
--- a/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
+++ b/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
@@ -75,7 +75,8 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         if (!(android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.showSetScreenLockDialog())) {
+                && android.multiuser.Flags.showSetScreenLockDialog()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures())) {
             finish();
             return;
         }
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index 4ef0a1b..97f8084 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -77,6 +77,7 @@
         }
 
         if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()
                 && !userManager.isManagedProfile(mUserId)) {
             Log.e(TAG, "Unlaunchable activity for target package " + targetPackageName
                     + " called for a non-managed-profile " + mUserId);
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 0dd01e4..2f1d654 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -406,6 +406,7 @@
             mElevation = preservedWindow.getElevation();
             mLoadElevation = false;
             mForceDecorInstall = true;
+            setSystemBarAppearance(preservedWindow.getSystemBarAppearance());
             // If we're preserving window, carry over the app token from the preserved
             // window, as we'll be skipping the addView in handleResumeActivity(), and
             // the token will not be updated as for a new window.
diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp
index 16c3ca9..1eff5ce 100644
--- a/core/jni/android_tracing_PerfettoDataSource.cpp
+++ b/core/jni/android_tracing_PerfettoDataSource.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "Perfetto"
+#define LOG_TAG "NativeJavaPerfettoDs"
 
 #include "android_tracing_PerfettoDataSource.h"
 
@@ -166,16 +166,25 @@
 
 void PerfettoDataSource::trace(JNIEnv* env, jobject traceFunction) {
     PERFETTO_DS_TRACE(dataSource, ctx) {
+        ALOG(LOG_DEBUG, LOG_TAG, "\tin native trace callback function %p", this);
         TlsState* tls_state =
                 reinterpret_cast<TlsState*>(PerfettoDsGetCustomTls(&dataSource, &ctx));
         IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(
                 PerfettoDsGetIncrementalState(&dataSource, &ctx));
 
+        ALOG(LOG_DEBUG, LOG_TAG, "\t tls_state = %p", tls_state);
+        ALOG(LOG_DEBUG, LOG_TAG, "\t incr_state = %p", incr_state);
+
+        ALOG(LOG_DEBUG, LOG_TAG, "\t tls_state->jobj = %p", tls_state->jobj);
+        ALOG(LOG_DEBUG, LOG_TAG, "\t incr_state->jobj = %p", incr_state->jobj);
+
         ScopedLocalRef<jobject> jCtx(env,
                                      env->NewObject(gTracingContextClassInfo.clazz,
                                                     gTracingContextClassInfo.init, &ctx,
                                                     tls_state->jobj, incr_state->jobj));
 
+        ALOG(LOG_DEBUG, LOG_TAG, "\t jCtx = %p", jCtx.get());
+
         jclass objclass = env->GetObjectClass(traceFunction);
         jmethodID method =
                 env->GetMethodID(objclass, "trace", "(Landroid/tracing/perfetto/TracingContext;)V");
@@ -209,7 +218,9 @@
 
 jlong nativeCreate(JNIEnv* env, jclass clazz, jobject javaDataSource, jstring name) {
     const char* nativeString = env->GetStringUTFChars(name, 0);
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeCreate(%p, %s)", javaDataSource, nativeString);
     PerfettoDataSource* dataSource = new PerfettoDataSource(env, javaDataSource, nativeString);
+    ALOG(LOG_DEBUG, LOG_TAG, "\tdatasource* = %p", dataSource);
     env->ReleaseStringUTFChars(name, nativeString);
 
     dataSource->incStrong((void*)nativeCreate);
@@ -218,33 +229,39 @@
 }
 
 void nativeDestroy(void* ptr) {
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeCreate(%p)", ptr);
     PerfettoDataSource* dataSource = reinterpret_cast<PerfettoDataSource*>(ptr);
     dataSource->decStrong((void*)nativeCreate);
 }
 
 static jlong nativeGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) {
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeGetFinalizer()");
     return static_cast<jlong>(reinterpret_cast<uintptr_t>(&nativeDestroy));
 }
 
 void nativeTrace(JNIEnv* env, jclass clazz, jlong dataSourcePtr, jobject traceFunctionInterface) {
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeTrace(%p)", (void*)dataSourcePtr);
     sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
 
     datasource->trace(env, traceFunctionInterface);
 }
 
 void nativeFlush(JNIEnv* env, jclass clazz, jobject jCtx, jlong ctxPtr) {
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeFlush(%p, %p)", jCtx, (void*)ctxPtr);
     auto* ctx = reinterpret_cast<struct PerfettoDsTracerIterator*>(ctxPtr);
     traceAllPendingPackets(env, jCtx, ctx);
     PerfettoDsTracerFlush(ctx, nullptr, nullptr);
 }
 
 void nativeFlushAll(JNIEnv* env, jclass clazz, jlong ptr) {
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeFlushAll(%p)", (void*)ptr);
     sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(ptr);
     datasource->flushAll();
 }
 
 void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
                               int buffer_exhausted_policy) {
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeRegisterDataSource(%p)", (void*)datasource_ptr);
     sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(datasource_ptr);
 
     struct PerfettoDsParams params = PerfettoDsParamsDefault();
@@ -267,6 +284,9 @@
         auto* datasource_instance =
                 new PerfettoDataSourceInstance(env, java_data_source_instance.get(), inst_id);
 
+        ALOG(LOG_DEBUG, LOG_TAG, "on_setup_cb ds=%p, ds_instance=%p", datasource,
+             datasource_instance);
+
         return static_cast<void*>(datasource_instance);
     };
 
@@ -280,6 +300,8 @@
 
         auto* tls_state = new TlsState(java_tls_state);
 
+        ALOG(LOG_DEBUG, LOG_TAG, "on_create_tls_cb ds=%p, tsl_state=%p", datasource, tls_state);
+
         return static_cast<void*>(tls_state);
     };
 
@@ -287,6 +309,9 @@
         JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
 
         TlsState* tls_state = reinterpret_cast<TlsState*>(ptr);
+
+        ALOG(LOG_DEBUG, LOG_TAG, "on_delete_tls_cb %p", tls_state);
+
         env->DeleteGlobalRef(tls_state->jobj);
         delete tls_state;
     };
@@ -299,6 +324,9 @@
         jobject java_incr_state = datasource->createIncrementalStateGlobalRef(env, inst_id);
 
         auto* incr_state = new IncrementalState(java_incr_state);
+
+        ALOG(LOG_DEBUG, LOG_TAG, "on_create_incr_cb ds=%p, incr_state=%p", datasource, incr_state);
+
         return static_cast<void*>(incr_state);
     };
 
@@ -306,6 +334,9 @@
         JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
 
         IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(ptr);
+
+        ALOG(LOG_DEBUG, LOG_TAG, "on_delete_incr_cb incr_state=%p", incr_state);
+
         env->DeleteGlobalRef(incr_state->jobj);
         delete incr_state;
     };
@@ -315,6 +346,9 @@
         JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
 
         auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+
+        ALOG(LOG_DEBUG, LOG_TAG, "on_start_cb ds_instance=%p", datasource_instance);
+
         datasource_instance->onStart(env);
     };
 
@@ -323,6 +357,9 @@
         JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
 
         auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+
+        ALOG(LOG_DEBUG, LOG_TAG, "on_flush_cb ds_instance=%p", datasource_instance);
+
         datasource_instance->onFlush(env);
     };
 
@@ -331,12 +368,18 @@
         JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
 
         auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+
+        ALOG(LOG_DEBUG, LOG_TAG, "on_stop_cb ds_instance=%p", datasource_instance);
+
         datasource_instance->onStop(env);
     };
 
     params.on_destroy_cb = [](struct PerfettoDsImpl* ds_impl, void* user_arg,
                               void* inst_ctx) -> void {
         auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+
+        ALOG(LOG_DEBUG, LOG_TAG, "on_destroy_cb ds_instance=%p", datasource_instance);
+
         delete datasource_instance;
     };
 
@@ -345,20 +388,28 @@
 
 jobject nativeGetPerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
                                         PerfettoDsInstanceIndex instance_idx) {
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeGetPerfettoInstanceLocked ds=%p, idx=%d", (void*)dataSourcePtr,
+         instance_idx);
     sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
     auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(
             PerfettoDsImplGetInstanceLocked(datasource->dataSource.impl, instance_idx));
 
     if (datasource_instance == nullptr) {
         // datasource instance doesn't exist
+        ALOG(LOG_WARN, LOG_TAG,
+             "DS instance invalid!! nativeGetPerfettoInstanceLocked returning NULL");
         return nullptr;
     }
 
+    ALOG(LOG_DEBUG, LOG_TAG, "\tnativeGetPerfettoInstanceLocked got lock ds=%p, idx=%d",
+         (void*)dataSourcePtr, instance_idx);
     return datasource_instance->GetJavaDataSourceInstance();
 }
 
 void nativeReleasePerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
                                          PerfettoDsInstanceIndex instance_idx) {
+    ALOG(LOG_DEBUG, LOG_TAG, "nativeReleasePerfettoInstanceLocked got lock ds=%p, idx=%d",
+         (void*)dataSourcePtr, instance_idx);
     sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
     PerfettoDsImplReleaseInstanceLocked(datasource->dataSource.impl, instance_idx);
 }
diff --git a/core/jni/android_tracing_PerfettoDataSource.h b/core/jni/android_tracing_PerfettoDataSource.h
index 61a7654..906d9f5 100644
--- a/core/jni/android_tracing_PerfettoDataSource.h
+++ b/core/jni/android_tracing_PerfettoDataSource.h
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "Perfetto"
-
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 #include <nativehelper/JNIHelp.h>
diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.h b/core/jni/android_tracing_PerfettoDataSourceInstance.h
index ebb5259..be71cbb 100644
--- a/core/jni/android_tracing_PerfettoDataSourceInstance.h
+++ b/core/jni/android_tracing_PerfettoDataSourceInstance.h
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "Perfetto"
-
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 #include <nativehelper/JNIHelp.h>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9d902c9..a0cb705 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1608,7 +1608,10 @@
     <fraction name="config_autoBrightnessAdjustmentMaxGamma">300%</fraction>
 
     <!-- If we allow automatic adjustment of screen brightness while dozing, how many times we want
-         to reduce it to preserve the battery. Value of 100% means no scaling. -->
+         to reduce it to preserve the battery. Value of 100% means no scaling. Not used if there is
+         a designated auto-brightness doze mapping defined in Display Device Config.
+         Also used to scale the brightness for the doze mode when auto-brightness is disabled if
+         there is an offload session present. -->
     <fraction name="config_screenAutoBrightnessDozeScaleFactor">100%</fraction>
 
     <!-- When the screen is turned on, the previous estimate of the ambient light level at the time
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 238772f..c7e08e2 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5452,7 +5452,7 @@
     <string name="set_up_screen_lock_title">Set a screen lock</string>
     <!-- Action label for the dialog prompting the user to set up a screen lock [CHAR LIMIT=30] -->
     <string name="set_up_screen_lock_action_label">Set screen lock</string>
-    <!-- Message shown in the dialog prompting the user to set up a screen lock to access private space [CHAR LIMIT=30] -->
+    <!-- Message shown in the dialog prompting the user to set up a screen lock to access private space [CHAR LIMIT=120] -->
     <string name="private_space_set_up_screen_lock_message">To use your private space, set a screen lock on this device</string>
 
     <!-- Title of the dialog that is shown when the user tries to launch a blocked application [CHAR LIMIT=50] -->
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index 5ac99db..89c2b3c 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -255,7 +255,7 @@
             assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
             assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
             assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
-            assertEquals(td1.getStatusBarAppearance(), td2.getStatusBarAppearance());
+            assertEquals(td1.getSystemBarsAppearance(), td2.getSystemBarsAppearance());
             assertEquals(td1.getResizeMode(), td2.getResizeMode());
             assertEquals(td1.getMinWidth(), td2.getMinWidth());
             assertEquals(td1.getMinHeight(), td2.getMinHeight());
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
new file mode 100644
index 0000000..90a8c5c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -0,0 +1,240 @@
+/*
+ * 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 android.view;
+
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ViewFrameRateTest {
+
+    @Rule
+    public ActivityTestRule<ViewCaptureTestActivity> mActivityRule = new ActivityTestRule<>(
+            ViewCaptureTestActivity.class);
+
+    private Activity mActivity;
+    private View mMovingView;
+    private ViewRootImpl mViewRoot;
+
+    @Before
+    public void setUp() throws Throwable {
+        mActivity = mActivityRule.getActivity();
+        mActivityRule.runOnUiThread(() -> {
+            mActivity.setContentView(R.layout.view_velocity_test);
+            mMovingView = mActivity.findViewById(R.id.moving_view);
+        });
+        ViewParent parent = mActivity.getWindow().getDecorView().getParent();
+        while (parent instanceof View) {
+            parent = parent.getParent();
+        }
+        mViewRoot = (ViewRootImpl) parent;
+    }
+
+    @UiThreadTest
+    @Test
+    @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+    public void frameRateChangesWhenContentMoves() {
+        mMovingView.offsetLeftAndRight(100);
+        float frameRate = mViewRoot.getPreferredFrameRate();
+        assertTrue(frameRate > 0);
+    }
+
+    @UiThreadTest
+    @Test
+    @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+    public void firstFrameNoMovement() {
+        assertEquals(0f, mViewRoot.getPreferredFrameRate(), 0f);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+    public void touchBoostDisable() throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            long now = SystemClock.uptimeMillis();
+            MotionEvent down = MotionEvent.obtain(
+                    /* downTime */ now,
+                    /* eventTime */ now,
+                    /* action */ MotionEvent.ACTION_DOWN,
+                    /* x */ 0f,
+                    /* y */ 0f,
+                    /* metaState */ 0
+            );
+            mActivity.dispatchTouchEvent(down);
+            mMovingView.offsetLeftAndRight(10);
+        });
+        mActivityRule.runOnUiThread(() -> {
+            mMovingView.invalidate();
+        });
+
+        mActivityRule.runOnUiThread(() -> {
+            assertFalse(mViewRoot.getIsTouchBoosting());
+        });
+    }
+
+    private void waitForFrameRateCategoryToSettle() throws Throwable {
+        for (int i = 0; i < 5; i++) {
+            final CountDownLatch drawLatch = new CountDownLatch(1);
+
+            // Now that it is small, any invalidation should have a normal category
+            mActivityRule.runOnUiThread(() -> {
+                mMovingView.invalidate();
+                mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch::countDown);
+            });
+
+            assertTrue(drawLatch.await(1, TimeUnit.SECONDS));
+        }
+    }
+
+    @Test
+    public void noVelocityUsesCategorySmall() throws Throwable {
+        final CountDownLatch drawLatch1 = new CountDownLatch(1);
+        mActivityRule.runOnUiThread(() -> {
+            float density = mActivity.getResources().getDisplayMetrics().density;
+            ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+            layoutParams.height = (int) (40 * density);
+            layoutParams.width = (int) (40 * density);
+            mMovingView.setLayoutParams(layoutParams);
+            mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+        });
+
+        assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+        waitForFrameRateCategoryToSettle();
+
+        // Now that it is small, any invalidation should have a normal category
+        mActivityRule.runOnUiThread(() -> {
+            mMovingView.invalidate();
+            assertEquals(Surface.FRAME_RATE_CATEGORY_NORMAL,
+                    mViewRoot.getPreferredFrameRateCategory());
+        });
+    }
+
+    @Test
+    public void noVelocityUsesCategoryNarrowWidth() throws Throwable {
+        final CountDownLatch drawLatch1 = new CountDownLatch(1);
+        mActivityRule.runOnUiThread(() -> {
+            float density = mActivity.getResources().getDisplayMetrics().density;
+            ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+            layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
+            layoutParams.width = (int) (10 * density);
+            mMovingView.setLayoutParams(layoutParams);
+            mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+        });
+
+        assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+        waitForFrameRateCategoryToSettle();
+
+        // Now that it is small, any invalidation should have a normal category
+        mActivityRule.runOnUiThread(() -> {
+            mMovingView.invalidate();
+            assertEquals(Surface.FRAME_RATE_CATEGORY_NORMAL,
+                    mViewRoot.getPreferredFrameRateCategory());
+        });
+    }
+
+    @Test
+    public void noVelocityUsesCategoryNarrowHeight() throws Throwable {
+        final CountDownLatch drawLatch1 = new CountDownLatch(1);
+        mActivityRule.runOnUiThread(() -> {
+            float density = mActivity.getResources().getDisplayMetrics().density;
+            ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+            layoutParams.height = (int) (10 * density);
+            layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+            mMovingView.setLayoutParams(layoutParams);
+            mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+        });
+
+        assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+        waitForFrameRateCategoryToSettle();
+
+        // Now that it is small, any invalidation should have a normal category
+        mActivityRule.runOnUiThread(() -> {
+            mMovingView.invalidate();
+            assertEquals(Surface.FRAME_RATE_CATEGORY_NORMAL,
+                    mViewRoot.getPreferredFrameRateCategory());
+        });
+    }
+
+    @Test
+    public void noVelocityUsesCategoryLargeWidth() throws Throwable {
+        final CountDownLatch drawLatch1 = new CountDownLatch(1);
+        mActivityRule.runOnUiThread(() -> {
+            float density = mActivity.getResources().getDisplayMetrics().density;
+            ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+            layoutParams.height = (int) (40 * density);
+            layoutParams.width = (int) Math.ceil(41 * density);
+            mMovingView.setLayoutParams(layoutParams);
+            mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+        });
+
+        assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+        waitForFrameRateCategoryToSettle();
+
+        // Now that it is small, any invalidation should have a high category
+        mActivityRule.runOnUiThread(() -> {
+            mMovingView.invalidate();
+            assertEquals(Surface.FRAME_RATE_CATEGORY_HIGH,
+                    mViewRoot.getPreferredFrameRateCategory());
+        });
+    }
+
+    @Test
+    public void noVelocityUsesCategoryLargeHeight() throws Throwable {
+        final CountDownLatch drawLatch1 = new CountDownLatch(1);
+        mActivityRule.runOnUiThread(() -> {
+            float density = mActivity.getResources().getDisplayMetrics().density;
+            ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+            layoutParams.height = (int) Math.ceil(41 * density);
+            layoutParams.width = (int) (40 * density);
+            mMovingView.setLayoutParams(layoutParams);
+            mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+        });
+
+        assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+        waitForFrameRateCategoryToSettle();
+
+        // Now that it is small, any invalidation should have a high category
+        mActivityRule.runOnUiThread(() -> {
+            mMovingView.invalidate();
+            assertEquals(Surface.FRAME_RATE_CATEGORY_HIGH,
+                    mViewRoot.getPreferredFrameRateCategory());
+        });
+    }
+}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 1a242ef..2544fcb 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -19,6 +19,7 @@
 import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
 import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
 import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY;
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
 import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
@@ -796,6 +797,38 @@
     }
 
     /**
+     * When velocity of a View is not equal to 0, we call setFrameRateCategory with HIGH.
+     * Also, we shouldn't call setFrameRate.
+     */
+    @Test
+    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, FLAG_VIEW_VELOCITY_API})
+    public void votePreferredFrameRate_voteFrameRateCategory_velocityToHigh() {
+        View view = new View(sContext);
+        WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+        wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+        wmlp.width = 1;
+        wmlp.height = 1;
+
+        sInstrumentation.runOnMainSync(() -> {
+            WindowManager wm = sContext.getSystemService(WindowManager.class);
+            wm.addView(view, wmlp);
+        });
+        sInstrumentation.waitForIdleSync();
+
+        ViewRootImpl viewRootImpl = view.getViewRootImpl();
+
+        sInstrumentation.runOnMainSync(() -> {
+            assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
+            view.setFrameContentVelocity(100);
+            view.invalidate();
+            assertTrue(viewRootImpl.getPreferredFrameRate() > 0);
+        });
+        sInstrumentation.waitForIdleSync();
+        assertEquals(viewRootImpl.getLastPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+        assertEquals(viewRootImpl.getLastPreferredFrameRate(), 0 , 0.1);
+    }
+
+    /**
      * We should boost the frame rate if the value of mInsetsAnimationRunning is true.
      */
     @Test
diff --git a/core/tests/coretests/src/android/view/ViewVelocityTest.java b/core/tests/coretests/src/android/view/ViewVelocityTest.java
deleted file mode 100644
index d437f7b..0000000
--- a/core/tests/coretests/src/android/view/ViewVelocityTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.app.Activity;
-import android.os.SystemClock;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.filters.SmallTest;
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.frameworks.coretests.R;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ViewVelocityTest {
-
-    @Rule
-    public ActivityTestRule<ViewCaptureTestActivity> mActivityRule = new ActivityTestRule<>(
-            ViewCaptureTestActivity.class);
-
-    private Activity mActivity;
-    private View mMovingView;
-    private ViewRootImpl mViewRoot;
-
-    @Before
-    public void setUp() throws Throwable {
-        mActivity = mActivityRule.getActivity();
-        mActivityRule.runOnUiThread(() -> {
-            mActivity.setContentView(R.layout.view_velocity_test);
-            mMovingView = mActivity.findViewById(R.id.moving_view);
-        });
-        ViewParent parent = mActivity.getWindow().getDecorView().getParent();
-        while (parent instanceof View) {
-            parent = parent.getParent();
-        }
-        mViewRoot = (ViewRootImpl) parent;
-    }
-
-    @UiThreadTest
-    @Test
-    @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
-    public void frameRateChangesWhenContentMoves() {
-        mMovingView.offsetLeftAndRight(100);
-        float frameRate = mViewRoot.getPreferredFrameRate();
-        assertTrue(frameRate > 0);
-    }
-
-    @UiThreadTest
-    @Test
-    @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
-    public void firstFrameNoMovement() {
-        assertEquals(0f, mViewRoot.getPreferredFrameRate(), 0f);
-    }
-
-    @Test
-    @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
-    public void touchBoostDisable() throws Throwable {
-        mActivityRule.runOnUiThread(() -> {
-            long now = SystemClock.uptimeMillis();
-            MotionEvent down = MotionEvent.obtain(
-                    /* downTime */ now,
-                    /* eventTime */ now,
-                    /* action */ MotionEvent.ACTION_DOWN,
-                    /* x */ 0f,
-                    /* y */ 0f,
-                    /* metaState */ 0
-            );
-            mActivity.dispatchTouchEvent(down);
-            mMovingView.offsetLeftAndRight(10);
-        });
-        mActivityRule.runOnUiThread(() -> {
-            mMovingView.invalidate();
-        });
-
-        mActivityRule.runOnUiThread(() -> {
-            assertFalse(mViewRoot.getIsTouchBoosting());
-        });
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index b209c7c..cb8754a 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -1162,7 +1162,8 @@
     @Test
     public void testTriggerFromPrivateProfile_withoutWorkProfile() throws RemoteException {
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         markPrivateProfileUserAvailable();
         Intent sendIntent = createSendImageIntent();
         List<ResolvedComponentInfo> privateResolvedComponentInfos =
@@ -1183,7 +1184,8 @@
     @Test
     public void testTriggerFromPrivateProfile_withWorkProfilePresent(){
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         ResolverActivity.ENABLE_TABBED_VIEW = false;
         markPrivateProfileUserAvailable();
         markWorkProfileUserAvailable();
@@ -1205,7 +1207,8 @@
     @Test
     public void testPrivateProfile_triggerFromPrimaryUser_withWorkProfilePresent(){
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         markPrivateProfileUserAvailable();
         markWorkProfileUserAvailable();
         Intent sendIntent = createSendImageIntent();
@@ -1228,7 +1231,8 @@
     @Test
     public void testPrivateProfile_triggerFromWorkProfile(){
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         markPrivateProfileUserAvailable();
         markWorkProfileUserAvailable();
         Intent sendIntent = createSendImageIntent();
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index ce2543a..a621642 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -88,5 +88,6 @@
         <permission name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" />
         <permission name="android.permission.READ_SEARCH_INDEXABLES" />
         <permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/>
+        <permission name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" />
     </privapp-permissions>
 </permissions>
diff --git a/graphics/java/android/graphics/pdf/PdfEditor.java b/graphics/java/android/graphics/pdf/PdfEditor.java
index 3cd709e..69e1982 100644
--- a/graphics/java/android/graphics/pdf/PdfEditor.java
+++ b/graphics/java/android/graphics/pdf/PdfEditor.java
@@ -25,7 +25,9 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
+
 import dalvik.system.CloseGuard;
+
 import libcore.io.IoUtils;
 
 import java.io.IOException;
@@ -37,6 +39,12 @@
  */
 public final class PdfEditor {
 
+    /**
+     * Any call the native pdfium code has to be single threaded as the library does not support
+     * parallel use.
+     */
+    private static final Object sPdfiumLock = new Object();
+
     private final CloseGuard mCloseGuard = CloseGuard.get();
 
     private long mNativeDocument;
@@ -79,7 +87,7 @@
         }
         mInput = input;
 
-        synchronized (PdfRenderer.sPdfiumLock) {
+        synchronized (sPdfiumLock) {
             mNativeDocument = nativeOpen(mInput.getFd(), size);
             try {
                 mPageCount = nativeGetPageCount(mNativeDocument);
@@ -112,7 +120,7 @@
         throwIfClosed();
         throwIfPageNotInDocument(pageIndex);
 
-        synchronized (PdfRenderer.sPdfiumLock) {
+        synchronized (sPdfiumLock) {
             mPageCount = nativeRemovePage(mNativeDocument, pageIndex);
         }
     }
@@ -138,12 +146,12 @@
             Point size = new Point();
             getPageSize(pageIndex, size);
 
-            synchronized (PdfRenderer.sPdfiumLock) {
+            synchronized (sPdfiumLock) {
                 nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.ni(),
                         0, 0, size.x, size.y);
             }
         } else {
-            synchronized (PdfRenderer.sPdfiumLock) {
+            synchronized (sPdfiumLock) {
                 nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.ni(),
                         clip.left, clip.top, clip.right, clip.bottom);
             }
@@ -161,7 +169,7 @@
         throwIfOutSizeNull(outSize);
         throwIfPageNotInDocument(pageIndex);
 
-        synchronized (PdfRenderer.sPdfiumLock) {
+        synchronized (sPdfiumLock) {
             nativeGetPageSize(mNativeDocument, pageIndex, outSize);
         }
     }
@@ -177,7 +185,7 @@
         throwIfOutMediaBoxNull(outMediaBox);
         throwIfPageNotInDocument(pageIndex);
 
-        synchronized (PdfRenderer.sPdfiumLock) {
+        synchronized (sPdfiumLock) {
             return nativeGetPageMediaBox(mNativeDocument, pageIndex, outMediaBox);
         }
     }
@@ -193,7 +201,7 @@
         throwIfMediaBoxNull(mediaBox);
         throwIfPageNotInDocument(pageIndex);
 
-        synchronized (PdfRenderer.sPdfiumLock) {
+        synchronized (sPdfiumLock) {
             nativeSetPageMediaBox(mNativeDocument, pageIndex, mediaBox);
         }
     }
@@ -209,7 +217,7 @@
         throwIfOutCropBoxNull(outCropBox);
         throwIfPageNotInDocument(pageIndex);
 
-        synchronized (PdfRenderer.sPdfiumLock) {
+        synchronized (sPdfiumLock) {
             return nativeGetPageCropBox(mNativeDocument, pageIndex, outCropBox);
         }
     }
@@ -225,7 +233,7 @@
         throwIfCropBoxNull(cropBox);
         throwIfPageNotInDocument(pageIndex);
 
-        synchronized (PdfRenderer.sPdfiumLock) {
+        synchronized (sPdfiumLock) {
             nativeSetPageCropBox(mNativeDocument, pageIndex, cropBox);
         }
     }
@@ -238,7 +246,7 @@
     public boolean shouldScaleForPrinting() {
         throwIfClosed();
 
-        synchronized (PdfRenderer.sPdfiumLock) {
+        synchronized (sPdfiumLock) {
             return nativeScaleForPrinting(mNativeDocument);
         }
     }
@@ -255,7 +263,7 @@
         try {
             throwIfClosed();
 
-            synchronized (PdfRenderer.sPdfiumLock) {
+            synchronized (sPdfiumLock) {
                 nativeWrite(mNativeDocument, output.getFd());
             }
         } finally {
@@ -287,7 +295,7 @@
 
     private void doClose() {
         if (mNativeDocument != 0) {
-            synchronized (PdfRenderer.sPdfiumLock) {
+            synchronized (sPdfiumLock) {
                 nativeClose(mNativeDocument);
             }
             mNativeDocument = 0;
diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java
deleted file mode 100644
index 4666963..0000000
--- a/graphics/java/android/graphics/pdf/PdfRenderer.java
+++ /dev/null
@@ -1,502 +0,0 @@
-/*
- * Copyright (C) 2014 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 android.graphics.pdf;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
-
-import com.android.internal.util.Preconditions;
-
-import dalvik.system.CloseGuard;
-
-import libcore.io.IoUtils;
-
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * <p>
- * This class enables rendering a PDF document. This class is not thread safe.
- * </p>
- * <p>
- * If you want to render a PDF, you create a renderer and for every page you want
- * to render, you open the page, render it, and close the page. After you are done
- * with rendering, you close the renderer. After the renderer is closed it should not
- * be used anymore. Note that the pages are rendered one by one, i.e. you can have
- * only a single page opened at any given time.
- * </p>
- * <p>
- * A typical use of the APIs to render a PDF looks like this:
- * </p>
- * <pre>
- * // create a new renderer
- * PdfRenderer renderer = new PdfRenderer(getSeekableFileDescriptor());
- *
- * // let us just render all pages
- * final int pageCount = renderer.getPageCount();
- * for (int i = 0; i < pageCount; i++) {
- *     Page page = renderer.openPage(i);
- *
- *     // say we render for showing on the screen
- *     page.render(mBitmap, null, null, Page.RENDER_MODE_FOR_DISPLAY);
- *
- *     // do stuff with the bitmap
- *
- *     // close the page
- *     page.close();
- * }
- *
- * // close the renderer
- * renderer.close();
- * </pre>
- *
- * <h3>Print preview and print output</h3>
- * <p>
- * If you are using this class to rasterize a PDF for printing or show a print
- * preview, it is recommended that you respect the following contract in order
- * to provide a consistent user experience when seeing a preview and printing,
- * i.e. the user sees a preview that is the same as the printout.
- * </p>
- * <ul>
- * <li>
- * Respect the property whether the document would like to be scaled for printing
- * as per {@link #shouldScaleForPrinting()}.
- * </li>
- * <li>
- * When scaling a document for printing the aspect ratio should be preserved.
- * </li>
- * <li>
- * Do not inset the content with any margins from the {@link android.print.PrintAttributes}
- * as the application is responsible to render it such that the margins are respected.
- * </li>
- * <li>
- * If document page size is greater than the printed media size the content should
- * be anchored to the upper left corner of the page for left-to-right locales and
- * top right corner for right-to-left locales.
- * </li>
- * </ul>
- *
- * @see #close()
- */
-public final class PdfRenderer implements AutoCloseable {
-    /**
-     * Any call the native pdfium code has to be single threaded as the library does not support
-     * parallel use.
-     */
-    final static Object sPdfiumLock = new Object();
-
-    private final CloseGuard mCloseGuard = CloseGuard.get();
-
-    private final Point mTempPoint = new Point();
-
-    private long mNativeDocument;
-
-    private final int mPageCount;
-
-    private ParcelFileDescriptor mInput;
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private Page mCurrentPage;
-
-    /** @hide */
-    @IntDef({
-        Page.RENDER_MODE_FOR_DISPLAY,
-        Page.RENDER_MODE_FOR_PRINT
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface RenderMode {}
-
-    /**
-     * Creates a new instance.
-     * <p>
-     * <strong>Note:</strong> The provided file descriptor must be <strong>seekable</strong>,
-     * i.e. its data being randomly accessed, e.g. pointing to a file.
-     * </p>
-     * <p>
-     * <strong>Note:</strong> This class takes ownership of the passed in file descriptor
-     * and is responsible for closing it when the renderer is closed.
-     * </p>
-     * <p>
-     * If the file is from an untrusted source it is recommended to run the renderer in a separate,
-     * isolated process with minimal permissions to limit the impact of security exploits.
-     * </p>
-     *
-     * @param input Seekable file descriptor to read from.
-     *
-     * @throws java.io.IOException If an error occurs while reading the file.
-     * @throws java.lang.SecurityException If the file requires a password or
-     *         the security scheme is not supported.
-     */
-    public PdfRenderer(@NonNull ParcelFileDescriptor input) throws IOException {
-        if (input == null) {
-            throw new NullPointerException("input cannot be null");
-        }
-
-        final long size;
-        try {
-            Os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
-            size = Os.fstat(input.getFileDescriptor()).st_size;
-        } catch (ErrnoException ee) {
-            throw new IllegalArgumentException("file descriptor not seekable");
-        }
-        mInput = input;
-
-        synchronized (sPdfiumLock) {
-            mNativeDocument = nativeCreate(mInput.getFd(), size);
-            try {
-                mPageCount = nativeGetPageCount(mNativeDocument);
-            } catch (Throwable t) {
-                nativeClose(mNativeDocument);
-                mNativeDocument = 0;
-                throw t;
-            }
-        }
-
-        mCloseGuard.open("close");
-    }
-
-    /**
-     * Closes this renderer. You should not use this instance
-     * after this method is called.
-     */
-    public void close() {
-        throwIfClosed();
-        throwIfPageOpened();
-        doClose();
-    }
-
-    /**
-     * Gets the number of pages in the document.
-     *
-     * @return The page count.
-     */
-    public int getPageCount() {
-        throwIfClosed();
-        return mPageCount;
-    }
-
-    /**
-     * Gets whether the document prefers to be scaled for printing.
-     * You should take this info account if the document is rendered
-     * for printing and the target media size differs from the page
-     * size.
-     *
-     * @return If to scale the document.
-     */
-    public boolean shouldScaleForPrinting() {
-        throwIfClosed();
-
-        synchronized (sPdfiumLock) {
-            return nativeScaleForPrinting(mNativeDocument);
-        }
-    }
-
-    /**
-     * Opens a page for rendering.
-     *
-     * @param index The page index.
-     * @return A page that can be rendered.
-     *
-     * @see android.graphics.pdf.PdfRenderer.Page#close() PdfRenderer.Page.close()
-     */
-    public Page openPage(int index) {
-        throwIfClosed();
-        throwIfPageOpened();
-        throwIfPageNotInDocument(index);
-        mCurrentPage = new Page(index);
-        return mCurrentPage;
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            if (mCloseGuard != null) {
-                mCloseGuard.warnIfOpen();
-            }
-
-            doClose();
-        } finally {
-            super.finalize();
-        }
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private void doClose() {
-        if (mCurrentPage != null) {
-            mCurrentPage.close();
-            mCurrentPage = null;
-        }
-
-        if (mNativeDocument != 0) {
-            synchronized (sPdfiumLock) {
-                nativeClose(mNativeDocument);
-            }
-            mNativeDocument = 0;
-        }
-
-        if (mInput != null) {
-            IoUtils.closeQuietly(mInput);
-            mInput = null;
-        }
-        mCloseGuard.close();
-    }
-
-    private void throwIfClosed() {
-        if (mInput == null) {
-            throw new IllegalStateException("Already closed");
-        }
-    }
-
-    private void throwIfPageOpened() {
-        if (mCurrentPage != null) {
-            throw new IllegalStateException("Current page not closed");
-        }
-    }
-
-    private void throwIfPageNotInDocument(int pageIndex) {
-        if (pageIndex < 0 || pageIndex >= mPageCount) {
-            throw new IllegalArgumentException("Invalid page index");
-        }
-    }
-
-    /**
-     * This class represents a PDF document page for rendering.
-     */
-    public final class Page implements AutoCloseable {
-
-        private final CloseGuard mCloseGuard = CloseGuard.get();
-
-        /**
-         * Mode to render the content for display on a screen.
-         */
-        public static final int RENDER_MODE_FOR_DISPLAY = 1;
-
-        /**
-         * Mode to render the content for printing.
-         */
-        public static final int RENDER_MODE_FOR_PRINT = 2;
-
-        private final int mIndex;
-        private final int mWidth;
-        private final int mHeight;
-
-        private long mNativePage;
-
-        private Page(int index) {
-            Point size = mTempPoint;
-            synchronized (sPdfiumLock) {
-                mNativePage = nativeOpenPageAndGetSize(mNativeDocument, index, size);
-            }
-            mIndex = index;
-            mWidth = size.x;
-            mHeight = size.y;
-            mCloseGuard.open("close");
-        }
-
-        /**
-         * Gets the page index.
-         *
-         * @return The index.
-         */
-        public int getIndex() {
-            return  mIndex;
-        }
-
-        /**
-         * Gets the page width in points (1/72").
-         *
-         * @return The width in points.
-         */
-        public int getWidth() {
-            return mWidth;
-        }
-
-        /**
-         * Gets the page height in points (1/72").
-         *
-         * @return The height in points.
-         */
-        public int getHeight() {
-            return mHeight;
-        }
-
-        /**
-         * Renders a page to a bitmap.
-         * <p>
-         * You may optionally specify a rectangular clip in the bitmap bounds. No rendering
-         * outside the clip will be performed, hence it is your responsibility to initialize
-         * the bitmap outside the clip.
-         * </p>
-         * <p>
-         * You may optionally specify a matrix to transform the content from page coordinates
-         * which are in points (1/72") to bitmap coordinates which are in pixels. If this
-         * matrix is not provided this method will apply a transformation that will fit the
-         * whole page to the destination clip if provided or the destination bitmap if no
-         * clip is provided.
-         * </p>
-         * <p>
-         * The clip and transformation are useful for implementing tile rendering where the
-         * destination bitmap contains a portion of the image, for example when zooming.
-         * Another useful application is for printing where the size of the bitmap holding
-         * the page is too large and a client can render the page in stripes.
-         * </p>
-         * <p>
-         * <strong>Note: </strong> The destination bitmap format must be
-         * {@link Config#ARGB_8888 ARGB}.
-         * </p>
-         * <p>
-         * <strong>Note: </strong> The optional transformation matrix must be affine as per
-         * {@link android.graphics.Matrix#isAffine() Matrix.isAffine()}. Hence, you can specify
-         * rotation, scaling, translation but not a perspective transformation.
-         * </p>
-         *
-         * @param destination Destination bitmap to which to render.
-         * @param destClip Optional clip in the bitmap bounds.
-         * @param transform Optional transformation to apply when rendering.
-         * @param renderMode The render mode.
-         *
-         * @see #RENDER_MODE_FOR_DISPLAY
-         * @see #RENDER_MODE_FOR_PRINT
-         */
-        public void render(@NonNull Bitmap destination, @Nullable Rect destClip,
-                           @Nullable Matrix transform, @RenderMode int renderMode) {
-            if (mNativePage == 0) {
-                throw new NullPointerException();
-            }
-
-            destination = Preconditions.checkNotNull(destination, "bitmap null");
-
-            if (destination.getConfig() != Config.ARGB_8888) {
-                throw new IllegalArgumentException("Unsupported pixel format");
-            }
-
-            if (destClip != null) {
-                if (destClip.left < 0 || destClip.top < 0
-                        || destClip.right > destination.getWidth()
-                        || destClip.bottom > destination.getHeight()) {
-                    throw new IllegalArgumentException("destBounds not in destination");
-                }
-            }
-
-            if (transform != null && !transform.isAffine()) {
-                 throw new IllegalArgumentException("transform not affine");
-            }
-
-            if (renderMode != RENDER_MODE_FOR_PRINT && renderMode != RENDER_MODE_FOR_DISPLAY) {
-                throw new IllegalArgumentException("Unsupported render mode");
-            }
-
-            if (renderMode == RENDER_MODE_FOR_PRINT && renderMode == RENDER_MODE_FOR_DISPLAY) {
-                throw new IllegalArgumentException("Only single render mode supported");
-            }
-
-            final int contentLeft = (destClip != null) ? destClip.left : 0;
-            final int contentTop = (destClip != null) ? destClip.top : 0;
-            final int contentRight = (destClip != null) ? destClip.right
-                    : destination.getWidth();
-            final int contentBottom = (destClip != null) ? destClip.bottom
-                    : destination.getHeight();
-
-            // If transform is not set, stretch page to whole clipped area
-            if (transform == null) {
-                int clipWidth = contentRight - contentLeft;
-                int clipHeight = contentBottom - contentTop;
-
-                transform = new Matrix();
-                transform.postScale((float)clipWidth / getWidth(),
-                        (float)clipHeight / getHeight());
-                transform.postTranslate(contentLeft, contentTop);
-            }
-
-            // FIXME: This code is planned to be outside the UI rendering module, so it should not
-            // be able to access native instances from Bitmap, Matrix, etc.
-            final long transformPtr = transform.ni();
-
-            synchronized (sPdfiumLock) {
-                nativeRenderPage(mNativeDocument, mNativePage, destination.getNativeInstance(),
-                        contentLeft, contentTop, contentRight, contentBottom, transformPtr,
-                        renderMode);
-            }
-        }
-
-        /**
-         * Closes this page.
-         *
-         * @see android.graphics.pdf.PdfRenderer#openPage(int)
-         */
-        @Override
-        public void close() {
-            throwIfClosed();
-            doClose();
-        }
-
-        @Override
-        protected void finalize() throws Throwable {
-            try {
-                if (mCloseGuard != null) {
-                    mCloseGuard.warnIfOpen();
-                }
-
-                doClose();
-            } finally {
-                super.finalize();
-            }
-        }
-
-        private void doClose() {
-            if (mNativePage != 0) {
-                synchronized (sPdfiumLock) {
-                    nativeClosePage(mNativePage);
-                }
-                mNativePage = 0;
-            }
-
-            mCloseGuard.close();
-            mCurrentPage = null;
-        }
-
-        private void throwIfClosed() {
-            if (mNativePage == 0) {
-                throw new IllegalStateException("Already closed");
-            }
-        }
-    }
-
-    private static native long nativeCreate(int fd, long size);
-    private static native void nativeClose(long documentPtr);
-    private static native int nativeGetPageCount(long documentPtr);
-    private static native boolean nativeScaleForPrinting(long documentPtr);
-    private static native void nativeRenderPage(long documentPtr, long pagePtr, long bitmapHandle,
-            int clipLeft, int clipTop, int clipRight, int clipBottom, long transformPtr,
-            int renderMode);
-    private static native long nativeOpenPageAndGetSize(long documentPtr, int pageIndex,
-            Point outSize);
-    private static native void nativeClosePage(long pagePtr);
-}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
index 9cd14fca..e422198 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
@@ -28,6 +28,7 @@
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.R
 import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT
+import com.android.wm.shell.common.bubbles.BubbleBarLocation
 import com.google.common.truth.Truth.assertThat
 import com.google.common.util.concurrent.MoreExecutors.directExecutor
 import org.junit.Before
@@ -486,6 +487,32 @@
                 positioner.screenRect.width() - paddings[0] - paddings[2])
     }
 
+    @Test
+    fun testIsBubbleBarOnLeft_defaultsToRight() {
+        positioner.bubbleBarLocation = BubbleBarLocation.DEFAULT
+        assertThat(positioner.isBubbleBarOnLeft).isFalse()
+
+        // Check that left and right return expected position
+        positioner.bubbleBarLocation = BubbleBarLocation.LEFT
+        assertThat(positioner.isBubbleBarOnLeft).isTrue()
+        positioner.bubbleBarLocation = BubbleBarLocation.RIGHT
+        assertThat(positioner.isBubbleBarOnLeft).isFalse()
+    }
+
+    @Test
+    fun testIsBubbleBarOnLeft_rtlEnabled_defaultsToLeft() {
+        positioner.update(defaultDeviceConfig.copy(isRtl = true))
+
+        positioner.bubbleBarLocation = BubbleBarLocation.DEFAULT
+        assertThat(positioner.isBubbleBarOnLeft).isTrue()
+
+        // Check that left and right return expected position
+        positioner.bubbleBarLocation = BubbleBarLocation.LEFT
+        assertThat(positioner.isBubbleBarOnLeft).isTrue()
+        positioner.bubbleBarLocation = BubbleBarLocation.RIGHT
+        assertThat(positioner.isBubbleBarOnLeft).isFalse()
+    }
+
     private val defaultYPosition: Float
         /**
          * Calculates the Y position bubbles should be placed based on the config. Based on the
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 96aaf02..9585842 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -103,6 +103,7 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
 import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.onehanded.OneHandedController;
@@ -708,6 +709,30 @@
         return mBubbleProperties.isBubbleBarEnabled() && mBubblePositioner.isLargeScreen();
     }
 
+    /**
+     * Returns current {@link BubbleBarLocation} if bubble bar is being used.
+     * Otherwise returns <code>null</code>
+     */
+    @Nullable
+    public BubbleBarLocation getBubbleBarLocation() {
+        if (canShowAsBubbleBar()) {
+            return mBubblePositioner.getBubbleBarLocation();
+        }
+        return null;
+    }
+
+    /**
+     * Update bubble bar location and trigger and update to listeners
+     */
+    public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+        if (canShowAsBubbleBar()) {
+            mBubblePositioner.setBubbleBarLocation(bubbleBarLocation);
+            BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
+            bubbleBarUpdate.bubbleBarLocation = bubbleBarLocation;
+            mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate);
+        }
+    }
+
     /** Whether this userId belongs to the current user. */
     private boolean isCurrentProfile(int userId) {
         return userId == UserHandle.USER_ALL
@@ -1179,7 +1204,7 @@
      */
     @VisibleForTesting
     public void expandStackAndSelectBubbleFromLauncher(String key, Rect bubbleBarBounds) {
-        mBubblePositioner.setBubbleBarPosition(bubbleBarBounds);
+        mBubblePositioner.setBubbleBarBounds(bubbleBarBounds);
 
         if (BubbleOverflow.KEY.equals(key)) {
             mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 6c2f925..61f0ed2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -165,7 +165,7 @@
          * used when {@link BubbleController#isShowingAsBubbleBar()} is true.
          */
         BubbleBarUpdate getInitialState() {
-            BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
+            BubbleBarUpdate bubbleBarUpdate = BubbleBarUpdate.createInitialState();
             bubbleBarUpdate.shouldShowEducation = shouldShowEducation;
             for (int i = 0; i < bubbles.size(); i++) {
                 bubbleBarUpdate.currentBubbleList.add(bubbles.get(i).asBubbleBarBubble());
@@ -255,7 +255,9 @@
      * Returns a bubble bar update populated with the current list of active bubbles.
      */
     public BubbleBarUpdate getInitialStateForBubbleBar() {
-        return mStateChange.getInitialState();
+        BubbleBarUpdate initialState = mStateChange.getInitialState();
+        initialState.bubbleBarLocation = mPositioner.getBubbleBarLocation();
+        return initialState;
     }
 
     public void setSuppressionChangedListener(Bubbles.BubbleMetadataFlagListener listener) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index a5853d6..b215b61 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -32,6 +32,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.wm.shell.R;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
 
 /**
  * Keeps track of display size, configuration, and specific bubble sizes. One place for all
@@ -95,6 +96,7 @@
     private PointF mRestingStackPosition;
 
     private boolean mShowingInBubbleBar;
+    private BubbleBarLocation mBubbleBarLocation = BubbleBarLocation.DEFAULT;
     private final Rect mBubbleBarBounds = new Rect();
 
     public BubblePositioner(Context context, WindowManager windowManager) {
@@ -797,14 +799,36 @@
         mShowingInBubbleBar = showingInBubbleBar;
     }
 
+    public void setBubbleBarLocation(BubbleBarLocation location) {
+        mBubbleBarLocation = location;
+    }
+
+    public BubbleBarLocation getBubbleBarLocation() {
+        return mBubbleBarLocation;
+    }
+
+    /**
+     * @return <code>true</code> when bubble bar is on the left and <code>false</code> when on right
+     */
+    public boolean isBubbleBarOnLeft() {
+        return mBubbleBarLocation.isOnLeft(mDeviceConfig.isRtl());
+    }
+
     /**
      * Sets the position of the bubble bar in display coordinates.
      */
-    public void setBubbleBarPosition(Rect bubbleBarBounds) {
+    public void setBubbleBarBounds(Rect bubbleBarBounds) {
         mBubbleBarBounds.set(bubbleBarBounds);
     }
 
     /**
+     * Returns the display coordinates of the bubble bar.
+     */
+    public Rect getBubbleBarBounds() {
+        return mBubbleBarBounds;
+    }
+
+    /**
      * How wide the expanded view should be when showing from the bubble bar.
      */
     public int getExpandedViewWidthForBubbleBar(boolean isOverflow) {
@@ -831,11 +855,4 @@
     public int getBubbleBarExpandedViewPadding() {
         return mExpandedViewPadding;
     }
-
-    /**
-     * Returns the display coordinates of the bubble bar.
-     */
-    public Rect getBubbleBarBounds() {
-        return mBubbleBarBounds;
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 8946f41..9eb9632 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -477,7 +477,7 @@
     private Point getExpandedViewRestPosition(Size size) {
         final int padding = mPositioner.getBubbleBarExpandedViewPadding();
         Point point = new Point();
-        if (mLayerView.isOnLeft()) {
+        if (mPositioner.isBubbleBarOnLeft()) {
             point.x = mPositioner.getInsets().left + padding;
         } else {
             point.x = mPositioner.getAvailableRect().width() - size.getWidth() - padding;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index 7d37d60..056598b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -19,6 +19,8 @@
 import android.annotation.SuppressLint
 import android.view.MotionEvent
 import android.view.View
+import com.android.wm.shell.bubbles.BubblePositioner
+import com.android.wm.shell.common.bubbles.BubbleBarLocation
 import com.android.wm.shell.common.bubbles.DismissView
 import com.android.wm.shell.common.bubbles.RelativeTouchListener
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject
@@ -29,7 +31,8 @@
     private val expandedView: BubbleBarExpandedView,
     private val dismissView: DismissView,
     private val animationHelper: BubbleBarAnimationHelper,
-    private val onDismissed: () -> Unit
+    private val bubblePositioner: BubblePositioner,
+    private val dragListener: DragListener
 ) {
 
     var isStuckToDismiss: Boolean = false
@@ -45,11 +48,11 @@
         magnetizedExpandedView.magnetListener = MagnetListener()
         magnetizedExpandedView.animateStuckToTarget =
             {
-                    target: MagnetizedObject.MagneticTarget,
-                    _: Float,
-                    _: Float,
-                    _: Boolean,
-                    after: (() -> Unit)? ->
+                target: MagnetizedObject.MagneticTarget,
+                _: Float,
+                _: Float,
+                _: Boolean,
+                after: (() -> Unit)? ->
                 animationHelper.animateIntoTarget(target, after)
             }
 
@@ -73,13 +76,34 @@
         }
     }
 
+    /** Listener to receive callback about dragging events */
+    interface DragListener {
+        /**
+         * Bubble bar [BubbleBarLocation] has changed as a result of dragging the expanded view.
+         *
+         * Triggered when drag gesture passes the middle of the screen and before touch up.
+         * Can be triggered multiple times per gesture.
+         *
+         * @param location new location of the bubble bar as a result of the ongoing drag operation
+         */
+        fun onLocationChanged(location: BubbleBarLocation)
+
+        /** Expanded view has been released in the dismiss target */
+        fun onReleasedInDismiss()
+    }
+
     private inner class HandleDragListener : RelativeTouchListener() {
 
         private var isMoving = false
+        private var screenCenterX: Int = -1
+        private var isOnLeft = false
 
         override fun onDown(v: View, ev: MotionEvent): Boolean {
             // While animating, don't allow new touch events
-            return !expandedView.isAnimating
+            if (expandedView.isAnimating) return false
+            screenCenterX = bubblePositioner.screenRect.centerX()
+            isOnLeft = bubblePositioner.isBubbleBarOnLeft
+            return true
         }
 
         override fun onMove(
@@ -97,6 +121,14 @@
             expandedView.translationX = expandedViewInitialTranslationX + dx
             expandedView.translationY = expandedViewInitialTranslationY + dy
             dismissView.show()
+
+            if (isOnLeft && ev.rawX > screenCenterX) {
+                isOnLeft = false
+                dragListener.onLocationChanged(BubbleBarLocation.RIGHT)
+            } else if (!isOnLeft && ev.rawX < screenCenterX) {
+                isOnLeft = true
+                dragListener.onLocationChanged(BubbleBarLocation.LEFT)
+            }
         }
 
         override fun onUp(
@@ -113,6 +145,7 @@
         }
 
         override fun onCancel(v: View, ev: MotionEvent, viewInitialX: Float, viewInitialY: Float) {
+            isStuckToDismiss = false
             finishDrag()
         }
 
@@ -127,30 +160,29 @@
 
     private inner class MagnetListener : MagnetizedObject.MagnetListener {
         override fun onStuckToTarget(
-                target: MagnetizedObject.MagneticTarget,
-                draggedObject: MagnetizedObject<*>
+            target: MagnetizedObject.MagneticTarget,
+            draggedObject: MagnetizedObject<*>
         ) {
             isStuckToDismiss = true
         }
 
         override fun onUnstuckFromTarget(
-                target: MagnetizedObject.MagneticTarget,
-                draggedObject: MagnetizedObject<*>,
-                velX: Float,
-                velY: Float,
-                wasFlungOut: Boolean
+            target: MagnetizedObject.MagneticTarget,
+            draggedObject: MagnetizedObject<*>,
+            velX: Float,
+            velY: Float,
+            wasFlungOut: Boolean
         ) {
             isStuckToDismiss = false
             animationHelper.animateUnstuckFromDismissView(target)
         }
 
         override fun onReleasedInTarget(
-                target: MagnetizedObject.MagneticTarget,
-                draggedObject: MagnetizedObject<*>
+            target: MagnetizedObject.MagneticTarget,
+            draggedObject: MagnetizedObject<*>
         ) {
-            onDismissed()
+            dragListener.onReleasedInDismiss()
             dismissView.hide()
         }
     }
 }
-
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 42799d9..3fb9f63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -33,6 +33,8 @@
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 
+import androidx.annotation.NonNull;
+
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubble;
 import com.android.wm.shell.bubbles.BubbleController;
@@ -42,6 +44,8 @@
 import com.android.wm.shell.bubbles.BubbleViewProvider;
 import com.android.wm.shell.bubbles.DeviceConfig;
 import com.android.wm.shell.bubbles.DismissViewUtils;
+import com.android.wm.shell.bubbles.bar.BubbleBarExpandedViewDragController.DragListener;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
 import com.android.wm.shell.common.bubbles.DismissView;
 
 import kotlin.Unit;
@@ -155,12 +159,6 @@
         return mIsExpanded;
     }
 
-    // TODO(b/313661121) - when dragging is implemented, check user setting first
-    /** Whether the expanded view is positioned on the left or right side of the screen. */
-    public boolean isOnLeft() {
-        return getLayoutDirection() == LAYOUT_DIRECTION_RTL;
-    }
-
     /** Shows the expanded view of the provided bubble. */
     public void showExpandedView(BubbleViewProvider b) {
         BubbleBarExpandedView expandedView = b.getBubbleBarExpandedView();
@@ -207,15 +205,23 @@
                 }
             });
 
+            DragListener dragListener = new DragListener() {
+                @Override
+                public void onLocationChanged(@NonNull BubbleBarLocation location) {
+                    mBubbleController.setBubbleBarLocation(location);
+                }
+
+                @Override
+                public void onReleasedInDismiss() {
+                    mBubbleController.dismissBubble(mExpandedBubble.getKey(), DISMISS_USER_GESTURE);
+                }
+            };
             mDragController = new BubbleBarExpandedViewDragController(
                     mExpandedView,
                     mDismissView,
                     mAnimationHelper,
-                    () -> {
-                        mBubbleController.dismissBubble(mExpandedBubble.getKey(),
-                                DISMISS_USER_GESTURE);
-                        return Unit.INSTANCE;
-                    });
+                    mPositioner,
+                    dragListener);
 
             addView(mExpandedView, new LayoutParams(width, height, Gravity.LEFT));
         }
@@ -352,7 +358,7 @@
         lp.width = width;
         lp.height = height;
         mExpandedView.setLayoutParams(lp);
-        if (isOnLeft()) {
+        if (mPositioner.isBubbleBarOnLeft()) {
             mExpandedView.setX(mPositioner.getInsets().left + padding);
         } else {
             mExpandedView.setX(mPositioner.getAvailableRect().width() - width - padding);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.kt
new file mode 100644
index 0000000..f0bdfde
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.wm.shell.common.bubbles
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * The location of the bubble bar.
+ */
+enum class BubbleBarLocation : Parcelable {
+    /**
+     * Place bubble bar at the default location for the chosen system language.
+     * If an RTL language is used, it is on the left. Otherwise on the right.
+     */
+    DEFAULT,
+    /** Default bubble bar location is overridden. Place bubble bar on the left. */
+    LEFT,
+    /** Default bubble bar location is overridden. Place bubble bar on the right. */
+    RIGHT;
+
+    /**
+     * Returns whether bubble bar is pinned to the left edge or right edge.
+     */
+    fun isOnLeft(isRtl: Boolean): Boolean {
+        if (this == DEFAULT) {
+            return isRtl
+        }
+        return this == LEFT
+    }
+
+    override fun describeContents(): Int {
+        return 0
+    }
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeString(name)
+    }
+
+    companion object {
+        @JvmField
+        val CREATOR = object : Parcelable.Creator<BubbleBarLocation> {
+            override fun createFromParcel(parcel: Parcel): BubbleBarLocation {
+                return parcel.readString()?.let { valueOf(it) } ?: DEFAULT
+            }
+
+            override fun newArray(size: Int) = arrayOfNulls<BubbleBarLocation>(size)
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
index fc627a8..e5f6c37 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
@@ -33,6 +33,7 @@
 
     public static final String BUNDLE_KEY = "update";
 
+    public final boolean initialState;
     public boolean expandedChanged;
     public boolean expanded;
     public boolean shouldShowEducation;
@@ -46,6 +47,8 @@
     public String suppressedBubbleKey;
     @Nullable
     public String unsupressedBubbleKey;
+    @Nullable
+    public BubbleBarLocation bubbleBarLocation;
 
     // This is only populated if bubbles have been removed.
     public List<RemovedBubble> removedBubbles = new ArrayList<>();
@@ -56,10 +59,17 @@
     // This is only populated the first time a listener is connected so it gets the current state.
     public List<BubbleInfo> currentBubbleList = new ArrayList<>();
 
+
     public BubbleBarUpdate() {
+        this(false);
+    }
+
+    private BubbleBarUpdate(boolean initialState) {
+        this.initialState = initialState;
     }
 
     public BubbleBarUpdate(Parcel parcel) {
+        initialState = parcel.readBoolean();
         expandedChanged = parcel.readBoolean();
         expanded = parcel.readBoolean();
         shouldShowEducation = parcel.readBoolean();
@@ -75,6 +85,8 @@
         parcel.readStringList(bubbleKeysInOrder);
         currentBubbleList = parcel.readParcelableList(new ArrayList<>(),
                 BubbleInfo.class.getClassLoader());
+        bubbleBarLocation = parcel.readParcelable(BubbleBarLocation.class.getClassLoader(),
+                BubbleBarLocation.class);
     }
 
     /**
@@ -89,12 +101,15 @@
                 || !bubbleKeysInOrder.isEmpty()
                 || suppressedBubbleKey != null
                 || unsupressedBubbleKey != null
-                || !currentBubbleList.isEmpty();
+                || !currentBubbleList.isEmpty()
+                || bubbleBarLocation != null;
     }
 
     @Override
     public String toString() {
-        return "BubbleBarUpdate{ expandedChanged=" + expandedChanged
+        return "BubbleBarUpdate{"
+                + " initialState=" + initialState
+                + " expandedChanged=" + expandedChanged
                 + " expanded=" + expanded
                 + " selectedBubbleKey=" + selectedBubbleKey
                 + " shouldShowEducation=" + shouldShowEducation
@@ -105,6 +120,7 @@
                 + " removedBubbles=" + removedBubbles
                 + " bubbles=" + bubbleKeysInOrder
                 + " currentBubbleList=" + currentBubbleList
+                + " bubbleBarLocation=" + bubbleBarLocation
                 + " }";
     }
 
@@ -115,6 +131,7 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeBoolean(initialState);
         parcel.writeBoolean(expandedChanged);
         parcel.writeBoolean(expanded);
         parcel.writeBoolean(shouldShowEducation);
@@ -126,6 +143,16 @@
         parcel.writeParcelableList(removedBubbles, flags);
         parcel.writeStringList(bubbleKeysInOrder);
         parcel.writeParcelableList(currentBubbleList, flags);
+        parcel.writeParcelable(bubbleBarLocation, flags);
+    }
+
+    /**
+     * Create update for initial set of values.
+     * <p>
+     * Used when bubble bar is newly created.
+     */
+    public static BubbleBarUpdate createInitialState() {
+        return new BubbleBarUpdate(true);
     }
 
     @NonNull
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 22ba708..7b84868 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -16,6 +16,10 @@
 
 package com.android.wm.shell.desktopmode;
 
+import static android.content.res.Configuration.SCREENLAYOUT_SIZE_XLARGE;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager.RunningTaskInfo;
 import android.os.SystemProperties;
 
 import com.android.window.flags.Flags;
@@ -66,6 +70,9 @@
     private static final boolean USE_ROUNDED_CORNERS = SystemProperties.getBoolean(
             "persist.wm.debug.desktop_use_rounded_corners", true);
 
+    private static final boolean ENFORCE_DISPLAY_RESTRICTIONS = SystemProperties.getBoolean(
+            "persist.wm.debug.desktop_mode_enforce_display_restrictions", true);
+
     /**
      * Return {@code true} if desktop windowing is enabled
      */
@@ -104,4 +111,21 @@
     public static boolean useRoundedCorners() {
         return USE_ROUNDED_CORNERS;
     }
+
+    /**
+     * Return whether the display size restrictions should be enforced.
+     */
+    public static boolean enforceDisplayRestrictions() {
+        return ENFORCE_DISPLAY_RESTRICTIONS;
+    }
+
+    /**
+     * Return {@code true} if the display associated with the task is at least of size
+     * {@link android.content.res.Configuration#SCREENLAYOUT_SIZE_XLARGE} or has been overridden to
+     * ignore the size constraint.
+     */
+    public static boolean meetsMinimumDisplayRequirements(@NonNull RunningTaskInfo taskInfo) {
+        return !enforceDisplayRestrictions()
+                || taskInfo.configuration.isLayoutSizeAtLeast(SCREENLAYOUT_SIZE_XLARGE);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 654409f..2c66fd6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -305,6 +305,12 @@
             task: RunningTaskInfo,
             wct: WindowContainerTransaction = WindowContainerTransaction()
     ) {
+        if (!DesktopModeStatus.meetsMinimumDisplayRequirements(task)) {
+            KtProtoLog.w(
+                WM_SHELL_DESKTOP_MODE, "DesktopTasksController: Cannot enter desktop, " +
+                        "display does not meet minimum size requirements")
+            return
+        }
         KtProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
             "DesktopTasksController: moveToDesktop taskId=%d",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 7650444..9dd4c19 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -820,14 +820,15 @@
 
     void startIntents(PendingIntent pendingIntent1, Intent fillInIntent1,
             @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
-            PendingIntent pendingIntent2, Intent fillInIntent2,
+            @Nullable PendingIntent pendingIntent2, Intent fillInIntent2,
             @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
             @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
             @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
                 "startIntents: intent1=%s intent2=%s position=%d snapPosition=%d",
-                pendingIntent1.getIntent(), pendingIntent2.getIntent(), splitPosition,
-                snapPosition);
+                pendingIntent1.getIntent(),
+                (pendingIntent2 != null ? pendingIntent2.getIntent() : "null"),
+                splitPosition, snapPosition);
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (pendingIntent2 == null) {
             options1 = options1 != null ? options1 : new Bundle();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 500e6f4..bf22193 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -1052,8 +1052,7 @@
                 && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
                 && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
                 && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop()
-                && mDisplayController.getDisplayContext(taskInfo.displayId)
-                .getResources().getConfiguration().smallestScreenWidthDp >= 600;
+                && DesktopModeStatus.meetsMinimumDisplayRequirements(taskInfo);
     }
 
     private void createWindowDecoration(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
index 5dd96ac..7a64a47 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
@@ -22,12 +22,12 @@
 
 val TaskInfo.isTransparentCaptionBarAppearance: Boolean
     get() {
-        val appearance = taskDescription?.statusBarAppearance ?: 0
+        val appearance = taskDescription?.systemBarsAppearance ?: 0
         return (appearance and APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) != 0
     }
 
 val TaskInfo.isLightCaptionBarAppearance: Boolean
     get() {
-        val appearance = taskDescription?.statusBarAppearance ?: 0
+        val appearance = taskDescription?.systemBarsAppearance ?: 0
         return (appearance and APPEARANCE_LIGHT_CAPTION_BARS) != 0
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
index 6dcae27..96bc4a1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -65,7 +65,7 @@
                     taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
                     Color.valueOf(taskDescription.statusBarColor).luminance() < 0.5
                 } else {
-                    taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
+                    taskDescription.systemBarsAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
                 }
             } ?: false
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index fa0aba5..48e396a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -49,6 +49,8 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.bubbles.BubbleData.TimeSource;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
+import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 
 import com.google.common.collect.ImmutableList;
 
@@ -1207,6 +1209,19 @@
         assertOverflowChangedTo(ImmutableList.of());
     }
 
+    @Test
+    public void test_getInitialStateForBubbleBar_includesInitialBubblesAndPosition() {
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        sendUpdatedEntryAtTime(mEntryA2, 2000);
+        mPositioner.setBubbleBarLocation(BubbleBarLocation.LEFT);
+
+        BubbleBarUpdate update = mBubbleData.getInitialStateForBubbleBar();
+        assertThat(update.currentBubbleList).hasSize(2);
+        assertThat(update.currentBubbleList.get(0).getKey()).isEqualTo(mEntryA2.getKey());
+        assertThat(update.currentBubbleList.get(1).getKey()).isEqualTo(mEntryA1.getKey());
+        assertThat(update.bubbleBarLocation).isEqualTo(BubbleBarLocation.LEFT);
+    }
+
     private void verifyUpdateReceived() {
         verify(mListener).applyUpdate(mUpdateCaptor.capture());
         reset(mListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleBarLocationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleBarLocationTest.kt
new file mode 100644
index 0000000..27e0b19
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleBarLocationTest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common.bubbles
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.bubbles.BubbleBarLocation.DEFAULT
+import com.android.wm.shell.common.bubbles.BubbleBarLocation.LEFT
+import com.android.wm.shell.common.bubbles.BubbleBarLocation.RIGHT
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class BubbleBarLocationTest : ShellTestCase() {
+
+    @Test
+    fun isOnLeft_rtlEnabled_defaultsToLeft() {
+        assertThat(DEFAULT.isOnLeft(isRtl = true)).isTrue()
+    }
+
+    @Test
+    fun isOnLeft_rtlDisabled_defaultsToRight() {
+        assertThat(DEFAULT.isOnLeft(isRtl = false)).isFalse()
+    }
+
+    @Test
+    fun isOnLeft_left_trueForAllLanguageDirections() {
+        assertThat(LEFT.isOnLeft(isRtl = false)).isTrue()
+        assertThat(LEFT.isOnLeft(isRtl = true)).isTrue()
+    }
+
+    @Test
+    fun isOnLeft_right_falseForAllLanguageDirections() {
+        assertThat(RIGHT.isOnLeft(isRtl = false)).isFalse()
+        assertThat(RIGHT.isOnLeft(isRtl = true)).isFalse()
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 35c803b..4c8a308 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -23,6 +23,8 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
 import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.content.res.Configuration.SCREENLAYOUT_SIZE_NORMAL
+import android.content.res.Configuration.SCREENLAYOUT_SIZE_XLARGE
 import android.os.Binder
 import android.testing.AndroidTestingRunner
 import android.view.Display.DEFAULT_DISPLAY
@@ -123,7 +125,7 @@
 
     @Before
     fun setUp() {
-        mockitoSession = mockitoSession().mockStatic(DesktopModeStatus::class.java).startMocking()
+        mockitoSession = mockitoSession().spyStatic(DesktopModeStatus::class.java).startMocking()
         whenever(DesktopModeStatus.isEnabled()).thenReturn(true)
 
         shellInit = Mockito.spy(ShellInit(testExecutor))
@@ -332,6 +334,45 @@
     }
 
     @Test
+    fun moveToDesktop_screenSizeBelowXLarge_doesNothing() {
+        val task = setUpFullscreenTask()
+
+        // Update screen layout to be below minimum size
+        task.configuration.screenLayout = SCREENLAYOUT_SIZE_NORMAL
+
+        controller.moveToDesktop(task)
+        verifyWCTNotExecuted()
+    }
+
+    @Test
+    fun moveToDesktop_screenSizeBelowXLarge_displayRestrictionsOverridden_taskIsMovedToDesktop() {
+        val task = setUpFullscreenTask()
+
+        // Update screen layout to be below minimum size
+        task.configuration.screenLayout = SCREENLAYOUT_SIZE_NORMAL
+
+        // Simulate enforce display restrictions system property overridden to false
+        whenever(DesktopModeStatus.enforceDisplayRestrictions()).thenReturn(false)
+
+        controller.moveToDesktop(task)
+
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+            .isEqualTo(WINDOWING_MODE_FREEFORM)
+    }
+
+    @Test
+    fun moveToDesktop_screenSizeXLarge_taskIsMovedToDesktop() {
+        val task = setUpFullscreenTask()
+
+        controller.moveToDesktop(task)
+
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+            .isEqualTo(WINDOWING_MODE_FREEFORM)
+    }
+
+    @Test
     fun moveToDesktop_otherFreeformTasksBroughtToFront() {
         val homeTask = setUpHomeTask()
         val freeformTask = setUpFreeformTask()
@@ -816,6 +857,7 @@
 
     private fun setUpFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
         val task = createFullscreenTask(displayId)
+        task.configuration.screenLayout = SCREENLAYOUT_SIZE_XLARGE
         whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
         runningTasks.add(task)
         return task
@@ -823,6 +865,7 @@
 
     private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
         val task = createSplitScreenTask(displayId)
+        task.configuration.screenLayout = SCREENLAYOUT_SIZE_XLARGE
         whenever(splitScreenController.isTaskInSplitScreen(task.taskId)).thenReturn(true)
         whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
         runningTasks.add(task)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 9bb5482..83519bb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -23,10 +23,14 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
 import android.content.Context
+import android.content.res.Configuration.SCREENLAYOUT_SIZE_NORMAL
+import android.content.res.Configuration.SCREENLAYOUT_SIZE_XLARGE
 import android.graphics.Rect
 import android.hardware.display.DisplayManager
 import android.hardware.display.VirtualDisplay
 import android.os.Handler
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.util.SparseArray
@@ -42,6 +46,9 @@
 import android.view.WindowInsets.Type.navigationBars
 import android.view.WindowInsets.Type.statusBars
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.window.flags.Flags
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
@@ -51,6 +58,7 @@
 import com.android.wm.shell.common.DisplayLayout
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeStatus
 import com.android.wm.shell.desktopmode.DesktopTasksController
 import com.android.wm.shell.sysui.KeyguardChangeListener
 import com.android.wm.shell.sysui.ShellCommandHandler
@@ -59,6 +67,7 @@
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
@@ -71,6 +80,7 @@
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
 import java.util.Optional
 import java.util.function.Supplier
 import org.mockito.Mockito
@@ -82,6 +92,10 @@
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
 class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
+    @JvmField
+    @Rule
+    val setFlagsRule = SetFlagsRule()
+
     @Mock private lateinit var mockDesktopModeWindowDecorFactory:
             DesktopModeWindowDecoration.Factory
     @Mock private lateinit var mockMainHandler: Handler
@@ -351,6 +365,54 @@
         inOrder.verify(windowDecorByTaskIdSpy).remove(task.taskId)
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    fun testWindowDecor_screenSizeBelowXLarge_decorNotCreated() {
+        val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+        // Update screen layout to be below minimum size
+        task.configuration.screenLayout = SCREENLAYOUT_SIZE_NORMAL
+
+        onTaskOpening(task)
+        verify(mockDesktopModeWindowDecorFactory, never())
+            .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    fun testWindowDecor_screenSizeBelowXLarge_displayRestrictionsOverridden_decorCreated() {
+        val mockitoSession: StaticMockitoSession = mockitoSession()
+            .strictness(Strictness.LENIENT)
+            .spyStatic(DesktopModeStatus::class.java)
+            .startMocking()
+        try {
+            // Simulate enforce display restrictions system property overridden to false
+            whenever(DesktopModeStatus.enforceDisplayRestrictions()).thenReturn(false)
+
+            val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+            // Update screen layout to be below minimum size
+            task.configuration.screenLayout = SCREENLAYOUT_SIZE_NORMAL
+            setUpMockDecorationsForTasks(task)
+
+            onTaskOpening(task)
+            verify(mockDesktopModeWindowDecorFactory)
+                .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+        } finally {
+            mockitoSession.finishMocking()
+        }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+    fun testWindowDecor_screenSizeXLarge_decorCreated() {
+        val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+        task.configuration.screenLayout = SCREENLAYOUT_SIZE_XLARGE
+        setUpMockDecorationsForTasks(task)
+
+        onTaskOpening(task)
+        verify(mockDesktopModeWindowDecorFactory)
+            .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+    }
+
     private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
         desktopModeWindowDecorViewModel.onTaskOpening(
                 task,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 9e62bd2..ba3f6dd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -176,7 +176,7 @@
     public void updateRelayoutParams_freeformAndTransparent_allowsInputFallthrough() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
-        taskInfo.taskDescription.setStatusBarAppearance(
+        taskInfo.taskDescription.setSystemBarsAppearance(
                 APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND);
         final RelayoutParams relayoutParams = new RelayoutParams();
 
@@ -194,7 +194,7 @@
     public void updateRelayoutParams_freeformButOpaque_disallowsInputFallthrough() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
-        taskInfo.taskDescription.setStatusBarAppearance(0);
+        taskInfo.taskDescription.setSystemBarsAppearance(0);
         final RelayoutParams relayoutParams = new RelayoutParams();
 
         DesktopModeWindowDecoration.updateRelayoutParams(
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 54f94f5..4486f55 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -428,7 +428,6 @@
                 "jni/MovieImpl.cpp",
                 "jni/pdf/PdfDocument.cpp",
                 "jni/pdf/PdfEditor.cpp",
-                "jni/pdf/PdfRenderer.cpp",
                 "jni/pdf/PdfUtils.cpp",
             ],
             shared_libs: [
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index 883f273..fb0cdb0 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -70,7 +70,6 @@
 extern int register_android_graphics_fonts_FontFamily(JNIEnv* env);
 extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env);
 extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env);
-extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env);
 extern int register_android_graphics_text_MeasuredText(JNIEnv* env);
 extern int register_android_graphics_text_LineBreaker(JNIEnv *env);
 extern int register_android_graphics_text_TextShaper(JNIEnv *env);
@@ -142,7 +141,6 @@
             REG_JNI(register_android_graphics_fonts_FontFamily),
             REG_JNI(register_android_graphics_pdf_PdfDocument),
             REG_JNI(register_android_graphics_pdf_PdfEditor),
-            REG_JNI(register_android_graphics_pdf_PdfRenderer),
             REG_JNI(register_android_graphics_text_MeasuredText),
             REG_JNI(register_android_graphics_text_LineBreaker),
             REG_JNI(register_android_graphics_text_TextShaper),
diff --git a/libs/hwui/jni/pdf/PdfRenderer.cpp b/libs/hwui/jni/pdf/PdfRenderer.cpp
deleted file mode 100644
index cc1f961..0000000
--- a/libs/hwui/jni/pdf/PdfRenderer.cpp
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2014 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.
- */
-
-#include "PdfUtils.h"
-
-#include "GraphicsJNI.h"
-#include "SkBitmap.h"
-#include "SkMatrix.h"
-#include "fpdfview.h"
-
-#include <vector>
-#include <utils/Log.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-namespace android {
-
-static const int RENDER_MODE_FOR_DISPLAY = 1;
-static const int RENDER_MODE_FOR_PRINT = 2;
-
-static struct {
-    jfieldID x;
-    jfieldID y;
-} gPointClassInfo;
-
-static jlong nativeOpenPageAndGetSize(JNIEnv* env, jclass thiz, jlong documentPtr,
-        jint pageIndex, jobject outSize) {
-    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
-
-    FPDF_PAGE page = FPDF_LoadPage(document, pageIndex);
-    if (!page) {
-        jniThrowException(env, "java/lang/IllegalStateException",
-                "cannot load page");
-        return -1;
-    }
-
-    double width = 0;
-    double height = 0;
-
-    int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height);
-    if (!result) {
-        jniThrowException(env, "java/lang/IllegalStateException",
-                    "cannot get page size");
-        return -1;
-    }
-
-    env->SetIntField(outSize, gPointClassInfo.x, width);
-    env->SetIntField(outSize, gPointClassInfo.y, height);
-
-    return reinterpret_cast<jlong>(page);
-}
-
-static void nativeClosePage(JNIEnv* env, jclass thiz, jlong pagePtr) {
-    FPDF_PAGE page = reinterpret_cast<FPDF_PAGE>(pagePtr);
-    FPDF_ClosePage(page);
-}
-
-static void nativeRenderPage(JNIEnv* env, jclass thiz, jlong documentPtr, jlong pagePtr,
-        jlong bitmapPtr, jint clipLeft, jint clipTop, jint clipRight, jint clipBottom,
-        jlong transformPtr, jint renderMode) {
-    FPDF_PAGE page = reinterpret_cast<FPDF_PAGE>(pagePtr);
-
-    SkBitmap skBitmap;
-    bitmap::toBitmap(bitmapPtr).getSkBitmap(&skBitmap);
-
-    const int stride = skBitmap.width() * 4;
-
-    FPDF_BITMAP bitmap = FPDFBitmap_CreateEx(skBitmap.width(), skBitmap.height(),
-            FPDFBitmap_BGRA, skBitmap.getPixels(), stride);
-
-    int renderFlags = FPDF_REVERSE_BYTE_ORDER;
-    if (renderMode == RENDER_MODE_FOR_DISPLAY) {
-        renderFlags |= FPDF_LCD_TEXT;
-    } else if (renderMode == RENDER_MODE_FOR_PRINT) {
-        renderFlags |= FPDF_PRINTING;
-    }
-
-    SkMatrix matrix = *reinterpret_cast<SkMatrix*>(transformPtr);
-    SkScalar transformValues[6];
-    if (!matrix.asAffine(transformValues)) {
-        jniThrowException(env, "java/lang/IllegalArgumentException",
-                "transform matrix has perspective. Only affine matrices are allowed.");
-        return;
-    }
-
-    FS_MATRIX transform = {transformValues[SkMatrix::kAScaleX], transformValues[SkMatrix::kASkewY],
-                           transformValues[SkMatrix::kASkewX], transformValues[SkMatrix::kAScaleY],
-                           transformValues[SkMatrix::kATransX],
-                           transformValues[SkMatrix::kATransY]};
-
-    FS_RECTF clip = {(float) clipLeft, (float) clipTop, (float) clipRight, (float) clipBottom};
-
-    FPDF_RenderPageBitmapWithMatrix(bitmap, page, &transform, &clip, renderFlags);
-
-    skBitmap.notifyPixelsChanged();
-}
-
-static const JNINativeMethod gPdfRenderer_Methods[] = {
-    {"nativeCreate", "(IJ)J", (void*) nativeOpen},
-    {"nativeClose", "(J)V", (void*) nativeClose},
-    {"nativeGetPageCount", "(J)I", (void*) nativeGetPageCount},
-    {"nativeScaleForPrinting", "(J)Z", (void*) nativeScaleForPrinting},
-    {"nativeRenderPage", "(JJJIIIIJI)V", (void*) nativeRenderPage},
-    {"nativeOpenPageAndGetSize", "(JILandroid/graphics/Point;)J", (void*) nativeOpenPageAndGetSize},
-    {"nativeClosePage", "(J)V", (void*) nativeClosePage}
-};
-
-int register_android_graphics_pdf_PdfRenderer(JNIEnv* env) {
-    int result = RegisterMethodsOrDie(
-            env, "android/graphics/pdf/PdfRenderer", gPdfRenderer_Methods,
-            NELEM(gPdfRenderer_Methods));
-
-    jclass clazz = FindClassOrDie(env, "android/graphics/Point");
-    gPointClassInfo.x = GetFieldIDOrDie(env, clazz, "x", "I");
-    gPointClassInfo.y = GetFieldIDOrDie(env, clazz, "y", "I");
-
-    return result;
-};
-
-};
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index b5f7caa..0d0af11 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -25,6 +25,7 @@
 #include <android/sync.h>
 #include <gui/TraceUtils.h>
 #include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
 #include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
 #include <include/gpu/ganesh/vk/GrVkDirectContext.h>
 #include <ui/FatVector.h>
@@ -597,15 +598,14 @@
                         close(fence_clone);
                         sync_wait(bufferInfo->dequeue_fence, -1 /* forever */);
                     } else {
-                        GrBackendSemaphore backendSemaphore;
-                        backendSemaphore.initVulkan(semaphore);
+                        GrBackendSemaphore beSemaphore = GrBackendSemaphores::MakeVk(semaphore);
                         // Skia will take ownership of the VkSemaphore and delete it once the wait
                         // has finished. The VkSemaphore also owns the imported fd, so it will
                         // close the fd when it is deleted.
-                        bufferInfo->skSurface->wait(1, &backendSemaphore);
+                        bufferInfo->skSurface->wait(1, &beSemaphore);
                         // The following flush blocks the GPU immediately instead of waiting for
                         // other drawing ops. It seems dequeue_fence is not respected otherwise.
-                        // TODO: remove the flush after finding why backendSemaphore is not working.
+                        // TODO: remove the flush after finding why beSemaphore is not working.
                         skgpu::ganesh::FlushAndSubmit(bufferInfo->skSurface.get());
                     }
                 }
@@ -626,7 +626,7 @@
     SharedSemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device,
                         VkSemaphore semaphore)
             : mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) {
-        mGrBackendSemaphore.initVulkan(semaphore);
+        mGrBackendSemaphore = GrBackendSemaphores::MakeVk(mSemaphore);
     }
 
     ~SharedSemaphoreInfo() { mDestroyFunction(mDevice, mSemaphore, nullptr); }
@@ -798,8 +798,7 @@
         return UNKNOWN_ERROR;
     }
 
-    GrBackendSemaphore beSemaphore;
-    beSemaphore.initVulkan(semaphore);
+    GrBackendSemaphore beSemaphore = GrBackendSemaphores::MakeVk(semaphore);
 
     // Skia will take ownership of the VkSemaphore and delete it once the wait has finished. The
     // VkSemaphore also owns the imported fd, so it will close the fd when it is deleted.
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index 6dc45a6..6a32c5a 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -202,11 +202,13 @@
                 && update.state.surfaceDrawn;
         bool becomingVisible = wantSurfaceVisibleAndDrawn && !update.state.surfaceVisible;
         bool becomingHidden = !wantSurfaceVisibleAndDrawn && update.state.surfaceVisible;
-        if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden
-                || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA
-                        | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER
-                        | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID
-                        | DIRTY_ICON_STYLE))))) {
+        if (update.state.surfaceControl != NULL &&
+            (becomingVisible || becomingHidden ||
+             (wantSurfaceVisibleAndDrawn &&
+              (update.state.dirty &
+               (DIRTY_ALPHA | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER |
+                DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID | DIRTY_ICON_STYLE |
+                DIRTY_DRAW_DROP_SHADOW))))) {
             needApplyTransaction = true;
 
             if (wantSurfaceVisibleAndDrawn
@@ -235,13 +237,15 @@
                         update.state.transformationMatrix.dtdy);
             }
 
-            if (wantSurfaceVisibleAndDrawn
-                    && (becomingVisible
-                            || (update.state.dirty & (DIRTY_HOTSPOT | DIRTY_ICON_STYLE)))) {
+            if (wantSurfaceVisibleAndDrawn &&
+                (becomingVisible ||
+                 (update.state.dirty &
+                  (DIRTY_HOTSPOT | DIRTY_ICON_STYLE | DIRTY_DRAW_DROP_SHADOW)))) {
                 Parcel p;
                 p.writeInt32(static_cast<int32_t>(update.state.icon.style));
                 p.writeFloat(update.state.icon.hotSpotX);
                 p.writeFloat(update.state.icon.hotSpotY);
+                p.writeBool(update.state.icon.drawNativeDropShadow);
 
                 // Pass cursor metadata in the sprite surface so that when Android is running as a
                 // client OS (e.g. ARC++) the host OS can get the requested cursor metadata and
@@ -388,12 +392,13 @@
     uint32_t dirty;
     if (icon.isValid()) {
         mLocked.state.icon.bitmap = icon.bitmap.copy(ANDROID_BITMAP_FORMAT_RGBA_8888);
-        if (!mLocked.state.icon.isValid()
-                || mLocked.state.icon.hotSpotX != icon.hotSpotX
-                || mLocked.state.icon.hotSpotY != icon.hotSpotY) {
+        if (!mLocked.state.icon.isValid() || mLocked.state.icon.hotSpotX != icon.hotSpotX ||
+            mLocked.state.icon.hotSpotY != icon.hotSpotY ||
+            mLocked.state.icon.drawNativeDropShadow != icon.drawNativeDropShadow) {
             mLocked.state.icon.hotSpotX = icon.hotSpotX;
             mLocked.state.icon.hotSpotY = icon.hotSpotY;
-            dirty = DIRTY_BITMAP | DIRTY_HOTSPOT;
+            mLocked.state.icon.drawNativeDropShadow = icon.drawNativeDropShadow;
+            dirty = DIRTY_BITMAP | DIRTY_HOTSPOT | DIRTY_DRAW_DROP_SHADOW;
         } else {
             dirty = DIRTY_BITMAP;
         }
@@ -404,7 +409,7 @@
         }
     } else if (mLocked.state.icon.isValid()) {
         mLocked.state.icon.bitmap.reset();
-        dirty = DIRTY_BITMAP | DIRTY_HOTSPOT | DIRTY_ICON_STYLE;
+        dirty = DIRTY_BITMAP | DIRTY_HOTSPOT | DIRTY_ICON_STYLE | DIRTY_DRAW_DROP_SHADOW;
     } else {
         return; // setting to invalid icon and already invalid so nothing to do
     }
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 04ecb38..35776e9 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -151,6 +151,7 @@
         DIRTY_HOTSPOT = 1 << 6,
         DIRTY_DISPLAY_ID = 1 << 7,
         DIRTY_ICON_STYLE = 1 << 8,
+        DIRTY_DRAW_DROP_SHADOW = 1 << 9,
     };
 
     /* Describes the state of a sprite.
diff --git a/libs/input/SpriteIcon.h b/libs/input/SpriteIcon.h
index 0939af4..7d45d02 100644
--- a/libs/input/SpriteIcon.h
+++ b/libs/input/SpriteIcon.h
@@ -40,7 +40,7 @@
     PointerIconStyle style{PointerIconStyle::TYPE_NULL};
     float hotSpotX{};
     float hotSpotY{};
-    bool drawNativeDropShadow{false};
+    bool drawNativeDropShadow{};
 
     inline bool isValid() const { return bitmap.isValid() && !bitmap.isEmpty(); }
 
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index 60ab1a4..a53a8ce 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -275,17 +275,23 @@
         if (o == null || getClass() != o.getClass()) return false;
 
         final AudioMix that = (AudioMix) o;
+        boolean tokenMatch = android.media.audiopolicy.Flags.audioMixOwnership()
+                ? Objects.equals(this.mToken, that.mToken)
+                : true;
         return Objects.equals(this.mRouteFlags, that.mRouteFlags)
             && Objects.equals(this.mRule, that.mRule)
             && Objects.equals(this.mMixType, that.mMixType)
             && Objects.equals(this.mFormat, that.mFormat)
-            && Objects.equals(this.mToken, that.mToken);
+            && tokenMatch;
     }
 
     /** @hide */
     @Override
     public int hashCode() {
-        return Objects.hash(mRouteFlags, mRule, mMixType, mFormat, mToken);
+        if (android.media.audiopolicy.Flags.audioMixOwnership()) {
+            return Objects.hash(mRouteFlags, mRule, mMixType, mFormat, mToken);
+        }
+        return Objects.hash(mRouteFlags, mRule, mMixType, mFormat);
     }
 
     @Override
diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types
index b795560..5cf807d 100644
--- a/mime/java-res/android.mime.types
+++ b/mime/java-res/android.mime.types
@@ -80,6 +80,7 @@
 ?audio/aac-adts aac
 ?audio/ac3 ac3 a52
 ?audio/amr amr
+?audio/x-gsm gsm
 ?audio/imelody imy
 ?audio/midi rtttl xmf
 ?audio/mobile-xmf mxmf
@@ -103,6 +104,7 @@
 ?image/x-adobe-dng dng
 ?image/x-fuji-raf raf
 ?image/x-icon ico
+?image/x-jg art
 ?image/x-nikon-nrw nrw
 ?image/x-panasonic-rw2 rw2
 ?image/x-pentax-pef pef
@@ -117,7 +119,7 @@
 ?text/x-vcard vcf
 
 ?video/3gpp2 3gpp2 3gp2 3g2
-?video/3gpp 3gpp
+?video/3gpp 3gpp 3gp
 ?video/avi avi
 ?video/m4v m4v
 ?video/mp4 m4v f4v mp4v mpeg4
@@ -153,6 +155,7 @@
 audio/x-mpegurl m3u m3u8
 image/jpeg jpg
 image/x-ms-bmp bmp
+image/x-photoshop psd
 text/plain txt
 text/x-c++hdr hpp
 text/x-c++src cpp
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 54f1421..9e0bb86 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -265,10 +265,11 @@
   }
 
   @FlaggedApi("android.nfc.nfc_read_polling_loop") public final class PollingFrame implements android.os.Parcelable {
-    ctor public PollingFrame(int, @Nullable byte[], int, int);
+    ctor public PollingFrame(int, @Nullable byte[], int, int, boolean);
     method public int describeContents();
     method @NonNull public byte[] getData();
     method public int getTimestamp();
+    method public boolean getTriggeredAutoTransact();
     method public int getType();
     method public int getVendorSpecificGain();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java
index 29d7bdf..7028c8f 100644
--- a/nfc/java/android/nfc/cardemulation/PollingFrame.java
+++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java
@@ -133,12 +133,23 @@
     @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
     public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
 
+    /**
+     * KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for whether this polling frame triggered
+     * autoTransact in the Bundle included in MSG_POLLING_LOOP.
+     *
+     * @hide
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public static final String KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT =
+            "android.nfc.cardemulation.TRIGGERED_AUTOTRANSACT";
+
 
     @PollingFrameType
     private final int mType;
     private final byte[] mData;
     private final int mGain;
     private final int mTimestamp;
+    private final boolean mTriggeredAutoTransact;
 
     public static final @NonNull Parcelable.Creator<PollingFrame> CREATOR =
             new Parcelable.Creator<>() {
@@ -159,14 +170,17 @@
         mData = (data == null) ? new byte[0] : data;
         mGain = frame.getInt(KEY_POLLING_LOOP_GAIN, -1);
         mTimestamp = frame.getInt(KEY_POLLING_LOOP_TIMESTAMP);
+        mTriggeredAutoTransact = frame.containsKey(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT)
+                && frame.getBoolean(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT);
     }
 
     public PollingFrame(@PollingFrameType int type, @Nullable byte[] data,
-            int gain, int timestamp) {
+            int gain, int timestamp, boolean triggeredAutoTransact) {
         mType = type;
         mData = data == null ? new byte[0] : data;
         mGain = gain;
         mTimestamp = timestamp;
+        mTriggeredAutoTransact = triggeredAutoTransact;
     }
 
     /**
@@ -210,6 +224,14 @@
         return mTimestamp;
     }
 
+    /**
+     * Returns whether this frame triggered the device to automatically disable observe mode and
+     * allow one transaction.
+     */
+    public boolean getTriggeredAutoTransact() {
+        return mTriggeredAutoTransact;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -233,6 +255,7 @@
         }
         frame.putByteArray(KEY_POLLING_LOOP_DATA, getData());
         frame.putInt(KEY_POLLING_LOOP_TIMESTAMP, getTimestamp());
+        frame.putBoolean(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT, getTriggeredAutoTransact());
         return frame;
     }
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 965ee86..642a377 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -21,6 +21,7 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
@@ -94,7 +95,7 @@
         label = {
             Row(
                 horizontalArrangement = Arrangement.SpaceBetween,
-                modifier = Modifier.fillMaxWidth().padding(
+                modifier = Modifier.fillMaxWidth().heightIn(min = 56.dp).padding(
                     // Total end padding should be 16dp, but the suggestion chip itself
                     // has 8dp horizontal elements padding
                     horizontal = 8.dp, vertical = 16.dp,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index bc0ea02..b9c9d89 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -18,7 +18,6 @@
 
 import android.credentials.flags.Flags.selectorUiImprovementsEnabled
 import android.graphics.drawable.Drawable
-import android.text.TextUtils
 import androidx.activity.compose.ManagedActivityResultLauncher
 import androidx.activity.result.ActivityResult
 import androidx.activity.result.IntentSenderRequest
@@ -431,6 +430,14 @@
 
         val hasSingleEntry = primaryPageCredentialEntryList.size +
                 primaryPageLockedEntryList.size == 1
+        val areAllPasswordsOnPrimaryScreen = primaryPageLockedEntryList.isEmpty() &&
+                primaryPageCredentialEntryList.all {
+                    it.sortedCredentialEntryList.first().credentialType == CredentialType.PASSWORD
+                }
+        val areAllPasskeysOnPrimaryScreen = primaryPageLockedEntryList.isEmpty() &&
+                primaryPageCredentialEntryList.all {
+                    it.sortedCredentialEntryList.first().credentialType == CredentialType.PASSKEY
+                }
         item {
             if (requestDisplayInfo.preferIdentityDocUi) {
                 HeadlineText(
@@ -447,28 +454,30 @@
                 HeadlineText(
                     text = stringResource(
                         if (hasSingleEntry) {
-                            val singleEntryType = primaryPageCredentialEntryList.firstOrNull()
-                                ?.sortedCredentialEntryList?.firstOrNull()?.credentialType
-                            if (singleEntryType == CredentialType.PASSKEY)
+                            if (areAllPasskeysOnPrimaryScreen)
                                 R.string.get_dialog_title_use_passkey_for
-                            else if (singleEntryType == CredentialType.PASSWORD)
+                            else if (areAllPasswordsOnPrimaryScreen)
                                 R.string.get_dialog_title_use_password_for
                             else if (authenticationEntryList.isNotEmpty())
                                 R.string.get_dialog_title_unlock_options_for
                             else R.string.get_dialog_title_use_sign_in_for
                         } else {
-                            if (authenticationEntryList.isNotEmpty() ||
-                                sortedUserNameToCredentialEntryList.any { perNameEntryList ->
-                                    perNameEntryList.sortedCredentialEntryList.any { entry ->
-                                        entry.credentialType != CredentialType.PASSWORD &&
-                                            entry.credentialType != CredentialType.PASSKEY
-                                    }
+                            if (areAllPasswordsOnPrimaryScreen)
+                                R.string.get_dialog_title_choose_password_for
+                            else if (areAllPasskeysOnPrimaryScreen)
+                                R.string.get_dialog_title_choose_passkey_for
+                            else if (primaryPageLockedEntryList.isNotEmpty() ||
+                                primaryPageCredentialEntryList.any {
+                                    it.sortedCredentialEntryList.first().credentialType !=
+                                            CredentialType.PASSWORD &&
+                                    it.sortedCredentialEntryList.first().credentialType !=
+                                            CredentialType.PASSKEY
                                 }
-                            ) // For an unknown / locked entry, it's not true that it is
+                            ) // An unknown typed / locked entry exists, and we can't say it is
                             // already saved, strictly speaking. Hence use a different title
-                            // without the mention of "saved"
+                            // without the mention of "saved".
                                 R.string.get_dialog_title_choose_sign_in_for
-                            else
+                            else // All entries on the primary screen are passkeys or passwords
                                 R.string.get_dialog_title_choose_saved_sign_in_for
                         },
                         requestDisplayInfo.appName
@@ -490,8 +499,11 @@
                                     showMoreForTruncatedEntry.value = it.hasVisualOverflow
                                 },
                                 hasSingleEntry = hasSingleEntry,
+                                hasSingleProvider = singleProviderId != null,
                                 shouldOverrideIcon = entry.isDefaultIconPreferredAsSingleProvider &&
                                         (singleProviderId != null),
+                                shouldRemoveTypeDisplayName = areAllPasswordsOnPrimaryScreen ||
+                                        areAllPasskeysOnPrimaryScreen
                         )
                     }
                     primaryPageLockedEntryList.forEach {
@@ -754,11 +766,19 @@
     enforceOneLine: Boolean = false,
     onTextLayout: (TextLayoutResult) -> Unit = {},
     // Make optional since the secondary page doesn't care about this value.
-    hasSingleEntry: Boolean? = null,
+    hasSingleEntry: Boolean = false,
     // For primary page only, if all display entries come from the same provider AND if that
     // provider has opted in via isDefaultIconPreferredAsSingleProvider, then we override the
     // display icon to the default icon for the given credential type.
     shouldOverrideIcon: Boolean = false,
+    // For primary page only, if all entries come from the same provider, then remove that provider
+    // name from each entry, since that provider icon + name will be shown front and central at
+    // the top of the bottom sheet.
+    hasSingleProvider: Boolean = false,
+    // For primary page only, if all visible entrise are of the same type and that type is passkey
+    // or password, then set this bit to true to remove the type display name from each entry for
+    // simplification, since that info is mentioned in the title.
+    shouldRemoveTypeDisplayName: Boolean = false,
 ) {
     val (username, displayName) = if (credentialEntryInfo.credentialType == CredentialType.PASSKEY)
         userAndDisplayNameForPasskey(
@@ -788,17 +808,16 @@
         entryHeadlineText = username,
         entrySecondLineText = displayName,
         entryThirdLineText =
-        (if (hasSingleEntry != null && hasSingleEntry)
-            if (credentialEntryInfo.credentialType == CredentialType.PASSKEY ||
-                    credentialEntryInfo.credentialType == CredentialType.PASSWORD)
-                emptyList()
+        (if (hasSingleEntry)
+            if (shouldRemoveTypeDisplayName) emptyList()
             // Still show the type display name for all non-password/passkey types since it won't be
             // mentioned in the bottom sheet heading.
             else listOf(credentialEntryInfo.credentialTypeDisplayName)
         else listOf(
-                credentialEntryInfo.credentialTypeDisplayName,
-                credentialEntryInfo.providerDisplayName
-        )).filterNot(TextUtils::isEmpty).let { itemsToDisplay ->
+                if (shouldRemoveTypeDisplayName) null
+                else credentialEntryInfo.credentialTypeDisplayName,
+                if (hasSingleProvider) null else credentialEntryInfo.providerDisplayName
+        )).filterNot{ it.isNullOrBlank() }.let { itemsToDisplay ->
             if (itemsToDisplay.isEmpty()) null
             else itemsToDisplay.joinToString(
                 separator = stringResource(R.string.get_dialog_sign_in_type_username_separator)
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index 98a5a67..79c810c 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -54,6 +54,7 @@
         "androidx.lifecycle_lifecycle-extensions",
         "android.content.pm.flags-aconfig-java",
         "android.os.flags-aconfig-java",
+        "android.multiuser.flags-aconfig-java",
     ],
 
     lint: {
@@ -85,6 +86,7 @@
         "androidx.lifecycle_lifecycle-extensions",
         "android.content.pm.flags-aconfig-java",
         "android.os.flags-aconfig-java",
+        "android.multiuser.flags-aconfig-java",
     ],
     aaptflags: ["--product tablet"],
 
@@ -118,6 +120,7 @@
         "androidx.lifecycle_lifecycle-extensions",
         "android.content.pm.flags-aconfig-java",
         "android.os.flags-aconfig-java",
+        "android.multiuser.flags-aconfig-java",
     ],
     aaptflags: ["--product tv"],
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index 221ca4f..8f5d07c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -166,6 +166,7 @@
                     messageBuilder.append(getString(
                             R.string.uninstall_application_text_current_user_clone_profile));
                 } else if (Flags.allowPrivateProfile()
+                        && android.multiuser.Flags.enablePrivateSpaceFeatures()
                         && customUserManager.isPrivateProfile()
                         && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
                     messageBuilder.append(
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
index 0fc1845..c6b6d36 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
@@ -235,7 +235,9 @@
                     messageString = context.getString(
                             R.string.uninstall_application_text_current_user_clone_profile
                     )
-                } else if (Flags.allowPrivateProfile() && customUserManager!!.isPrivateProfile()) {
+                } else if (Flags.allowPrivateProfile()
+                        && android.multiuser.Flags.enablePrivateSpaceFeatures()
+                        && customUserManager!!.isPrivateProfile()) {
                     // TODO(b/324244123): Get these Strings from a User Property API.
                     messageString = context.getString(
                             R.string.uninstall_application_text_current_user_private_profile
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
index fc8de80..0a469b8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
@@ -47,7 +47,7 @@
                 onCheckedChange = model.onCheckedChange,
                 paddingStart = 20.dp,
                 paddingEnd = 20.dp,
-                paddingVertical = 18.dp,
+                paddingVertical = 24.dp,
             )
         }
     }
@@ -55,7 +55,7 @@
 
 @Preview
 @Composable
-fun MainSwitchPreferencePreview() {
+private fun MainSwitchPreferencePreview() {
     SettingsTheme {
         Column {
             MainSwitchPreference(object : SwitchPreferenceModel {
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index e489bc5..2889ce2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1703,6 +1703,7 @@
 
         public boolean isPrivateProfile() {
             return android.os.Flags.allowPrivateProfile()
+                    && android.multiuser.Flags.enablePrivateSpaceFeatures()
                     && UserManager.USER_TYPE_PROFILE_PRIVATE.equals(mProfileType);
         }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
index ff4d4dd..2b8c2dd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -37,6 +37,23 @@
  */
 // TODO - b/293578081: Remove once PackageNotAvailableException is propagated to library clients.
 /* package */ final class NoOpInfoMediaManager extends InfoMediaManager {
+    /**
+     * Placeholder routing session to return as active session of {@link NoOpInfoMediaManager}.
+     *
+     * <p>Returning this routing session avoids crashes in {@link InfoMediaManager} and maintains
+     * the same client-facing behaviour as if no routing session was found for the target package
+     * name.
+     *
+     * <p>Volume and max volume are set to {@code -1} to emulate a non-existing routing session in
+     * {@link #getSessionVolume()} and {@link #getSessionVolumeMax()}.
+     */
+    private static final RoutingSessionInfo PLACEHOLDER_SESSION =
+            new RoutingSessionInfo.Builder(
+                            /* id */ "FAKE_ROUTING_SESSION", /* clientPackageName */ "")
+                    .addSelectedRoute(/* routeId */ "FAKE_SELECTED_ROUTE_ID")
+                    .setVolumeMax(-1)
+                    .setVolume(-1)
+                    .build();
 
     NoOpInfoMediaManager(
             Context context,
@@ -118,7 +135,7 @@
     @NonNull
     @Override
     protected List<RoutingSessionInfo> getRoutingSessionsForPackage() {
-        return Collections.emptyList();
+        return List.of(PLACEHOLDER_SESSION);
     }
 
     @Nullable
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt
index a5c63be..7e3f38b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt
@@ -18,23 +18,18 @@
 
 import android.media.AudioDeviceAttributes
 import android.media.Spatializer
-import androidx.concurrent.futures.DirectExecutor
 import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
 interface SpatializerRepository {
 
-    /** Returns true when head tracking is enabled and false the otherwise. */
-    val isHeadTrackingAvailable: StateFlow<Boolean>
+    /**
+     * Returns true when head tracking is available for the [audioDeviceAttributes] and false the
+     * otherwise.
+     */
+    suspend fun isHeadTrackingAvailableForDevice(
+        audioDeviceAttributes: AudioDeviceAttributes
+    ): Boolean
 
     /**
      * Returns true when Spatial audio feature is supported for the [audioDeviceAttributes] and
@@ -65,22 +60,14 @@
 
 class SpatializerRepositoryImpl(
     private val spatializer: Spatializer,
-    coroutineScope: CoroutineScope,
     private val backgroundContext: CoroutineContext,
 ) : SpatializerRepository {
 
-    override val isHeadTrackingAvailable: StateFlow<Boolean> =
-        callbackFlow {
-                val listener =
-                    Spatializer.OnHeadTrackerAvailableListener { _, available ->
-                        launch { send(available) }
-                    }
-                spatializer.addOnHeadTrackerAvailableListener(DirectExecutor.INSTANCE, listener)
-                awaitClose { spatializer.removeOnHeadTrackerAvailableListener(listener) }
-            }
-            .onStart { emit(spatializer.isHeadTrackerAvailable) }
-            .flowOn(backgroundContext)
-            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), false)
+    override suspend fun isHeadTrackingAvailableForDevice(
+        audioDeviceAttributes: AudioDeviceAttributes
+    ): Boolean {
+        return withContext(backgroundContext) { spatializer.hasHeadTracker(audioDeviceAttributes) }
+    }
 
     override suspend fun isSpatialAudioAvailableForDevice(
         audioDeviceAttributes: AudioDeviceAttributes
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt
index 0347403..5589733 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt
@@ -18,17 +18,17 @@
 
 import android.media.AudioDeviceAttributes
 import com.android.settingslib.media.data.repository.SpatializerRepository
-import kotlinx.coroutines.flow.StateFlow
 
 class SpatializerInteractor(private val repository: SpatializerRepository) {
 
-    /** Checks if head tracking is available. */
-    val isHeadTrackingAvailable: StateFlow<Boolean>
-        get() = repository.isHeadTrackingAvailable
-
+    /** Checks if spatial audio is available. */
     suspend fun isSpatialAudioAvailable(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
         repository.isSpatialAudioAvailableForDevice(audioDeviceAttributes)
 
+    /** Checks if head tracking is available. */
+    suspend fun isHeadTrackingAvailable(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
+        repository.isHeadTrackingAvailableForDevice(audioDeviceAttributes)
+
     /** Checks if spatial audio is enabled for the [audioDeviceAttributes]. */
     suspend fun isSpatialAudioEnabled(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
         repository.getSpatialAudioCompatibleDevices().contains(audioDeviceAttributes)
diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt
index 794cf83..7719c4b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt
@@ -48,6 +48,9 @@
     /** Checks if [notificationPolicy] allows media. */
     val isMediaAllowed: Flow<Boolean?> = notificationPolicy.map { it?.allowMedia() }
 
+    /** Checks if [notificationPolicy] allows system sounds. */
+    val isSystemAllowed: Flow<Boolean?> = notificationPolicy.map { it?.allowSystem() }
+
     /** Checks if [notificationPolicy] allows ringer. */
     val isRingerAllowed: Flow<Boolean?> =
         notificationPolicy.map { policy ->
@@ -62,31 +65,29 @@
             areAlarmsAllowed.filterNotNull(),
             isMediaAllowed.filterNotNull(),
             isRingerAllowed.filterNotNull(),
-        ) { zenMode, areAlarmsAllowed, isMediaAllowed, isRingerAllowed ->
-            if (zenMode.zenMode == Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) {
-                return@combine true
+            isSystemAllowed.filterNotNull(),
+        ) { zenMode, areAlarmsAllowed, isMediaAllowed, isRingerAllowed, isSystemAllowed ->
+            when (zenMode.zenMode) {
+                // Everything is muted
+                Settings.Global.ZEN_MODE_NO_INTERRUPTIONS -> return@combine true
+                Settings.Global.ZEN_MODE_ALARMS ->
+                    return@combine stream.value == AudioManager.STREAM_RING ||
+                        stream.value == AudioManager.STREAM_NOTIFICATION ||
+                        stream.value == AudioManager.STREAM_SYSTEM
+                Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS -> {
+                    when {
+                        stream.value == AudioManager.STREAM_ALARM && !areAlarmsAllowed ->
+                            return@combine true
+                        stream.value == AudioManager.STREAM_MUSIC && !isMediaAllowed ->
+                            return@combine true
+                        stream.value == AudioManager.STREAM_SYSTEM && !isSystemAllowed ->
+                            return@combine true
+                        (stream.value == AudioManager.STREAM_RING ||
+                            stream.value == AudioManager.STREAM_NOTIFICATION) && !isRingerAllowed ->
+                            return@combine true
+                    }
+                }
             }
-
-            val isNotificationOrRing =
-                stream.value == AudioManager.STREAM_RING ||
-                    stream.value == AudioManager.STREAM_NOTIFICATION
-            if (isNotificationOrRing && zenMode.zenMode == Settings.Global.ZEN_MODE_ALARMS) {
-                return@combine true
-            }
-            if (zenMode.zenMode != Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
-                return@combine false
-            }
-
-            if (stream.value == AudioManager.STREAM_ALARM && !areAlarmsAllowed) {
-                return@combine true
-            }
-            if (stream.value == AudioManager.STREAM_MUSIC && !isMediaAllowed) {
-                return@combine true
-            }
-            if (isNotificationOrRing && !isRingerAllowed) {
-                return@combine true
-            }
-
             return@combine false
         }
     }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index 1ad7d49..fe83ffb 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -319,7 +319,8 @@
 
     @Test
     public void testPrivateProfileFilterDisplaysCorrectApps() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
 
         mEntry.showInPersonalTab = true;
         mEntry.mProfileType = UserManager.USER_TYPE_FULL_SYSTEM;
@@ -334,7 +335,8 @@
 
     @Test
     public void testPrivateProfileFilterDisplaysCorrectAppsWhenFlagDisabled() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
 
         mEntry.showInPersonalTab = false;
         mEntry.mProfileType = UserManager.USER_TYPE_PROFILE_PRIVATE;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
new file mode 100644
index 0000000..d630301
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.settingslib.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/**
+ * Tests for {@link NoOpInfoMediaManager} to avoid exceptions in {@link InfoMediaManager}.
+ *
+ * <p>While {@link NoOpInfoMediaManager} should not perform any actions, it should still return
+ * placeholder information in certain cases to not change the behaviour of {@link InfoMediaManager}
+ * and prevent crashes.
+ */
+@RunWith(RobolectricTestRunner.class)
+public class NoOpInfoMediaManagerTest {
+    private InfoMediaManager mInfoMediaManager;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+        mInfoMediaManager =
+                new NoOpInfoMediaManager(
+                        mContext,
+                        /* packageName */ "FAKE_PACKAGE_NAME",
+                        /* localBluetoothManager */ null);
+    }
+
+    @Test
+    public void getSessionVolumeMax_returnsNotFound() {
+        assertThat(mInfoMediaManager.getSessionVolumeMax()).isEqualTo(-1);
+    }
+
+    @Test
+    public void getSessionVolume_returnsNotFound() {
+        assertThat(mInfoMediaManager.getSessionVolume()).isEqualTo(-1);
+    }
+
+    @Test
+    public void getSessionName_returnsNull() {
+        assertThat(mInfoMediaManager.getSessionName()).isNull();
+    }
+
+    @Test
+    public void getRoutingSessionForPackage_returnsPlaceholderSession() {
+        // Make sure we return a placeholder routing session so that we avoid OOB exceptions.
+        assertThat(mInfoMediaManager.getRoutingSessionsForPackage()).hasSize(1);
+    }
+
+    @Test
+    public void getSelectedMediaDevices_returnsEmptyList() {
+        assertThat(mInfoMediaManager.getSelectedMediaDevices()).isEmpty();
+    }
+}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 04cb88d..617df74 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -155,6 +155,7 @@
         "jsr330",
         "lottie",
         "LowLightDreamLib",
+        "TraceurCommon",
         "motion_tool_lib",
         "notification_flags_lib",
         "PlatformComposeCore",
@@ -301,7 +302,9 @@
         "androidx.compose.material_material-icons-extended",
         "androidx.activity_activity-compose",
         "androidx.compose.animation_animation-graphics",
+        "TraceurCommon",
     ],
+    skip_jarjar_repackage: true,
 }
 
 android_library {
@@ -352,6 +355,7 @@
         test: true,
         extra_check_modules: ["SystemUILintChecker"],
     },
+    skip_jarjar_repackage: true,
 }
 
 android_app {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 12e8f57..98591e9 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -273,6 +273,9 @@
     <!-- to control accessibility volume -->
     <uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" />
 
+    <!-- to change spatial audio -->
+    <uses-permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" />
+
     <!-- to access ResolverRankerServices -->
     <uses-permission android:name="android.permission.BIND_RESOLVER_RANKER_SERVICE" />
 
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
new file mode 100644
index 0000000..a066b38
--- /dev/null
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
@@ -0,0 +1,21 @@
+/*
+ * 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
+
+import dagger.Module
+
+@Module interface SpatialAudioModule
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 078da1c86..eb6d303 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -66,6 +66,7 @@
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.State
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.derivedStateOf
@@ -74,6 +75,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
@@ -93,6 +95,7 @@
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.testTagsAsResourceId
@@ -118,6 +121,7 @@
 import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.res.R
 import kotlinx.coroutines.launch
@@ -150,6 +154,8 @@
     val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
     val contentOffset = beforeContentPadding(contentPadding).toOffset()
 
+    ScrollOnNewSmartspaceEffect(viewModel, gridState)
+
     Box(
         modifier =
             modifier
@@ -218,6 +224,17 @@
             widgetConfigurator = widgetConfigurator,
         )
 
+        // TODO(b/326060686): Remove this once keyguard indication area can persist over hub
+        if (viewModel is CommunalViewModel) {
+            val isUnlocked by viewModel.deviceUnlocked.collectAsState(initial = false)
+            LockStateIcon(
+                modifier =
+                    Modifier.align(Alignment.BottomCenter)
+                        .padding(bottom = Dimensions.LockIconBottomPadding),
+                isUnlocked = isUnlocked,
+            )
+        }
+
         if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
             Toolbar(
                 isDraggingToRemove = isDraggingToRemove,
@@ -266,6 +283,34 @@
     }
 }
 
+@Composable
+private fun ScrollOnNewSmartspaceEffect(
+    viewModel: BaseCommunalViewModel,
+    gridState: LazyGridState
+) {
+    val communalContent by viewModel.communalContent.collectAsState(initial = emptyList())
+    var smartspaceCount by remember { mutableStateOf(0) }
+
+    LaunchedEffect(communalContent) {
+        snapshotFlow { gridState.firstVisibleItemIndex }
+            .collect { index ->
+                val existingSmartspaceCount = smartspaceCount
+                smartspaceCount = communalContent.count { it.isSmartspace() }
+                val firstIndex = communalContent.indexOfFirst { it.isSmartspace() }
+
+                // Scroll to the beginning of the smartspace area whenever the number of
+                // smartspace elements grows
+                if (
+                    existingSmartspaceCount < smartspaceCount &&
+                        !viewModel.isEditMode &&
+                        index > firstIndex
+                ) {
+                    gridState.animateScrollToItem(firstIndex)
+                }
+            }
+    }
+}
+
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun BoxScope.CommunalHubLazyGrid(
@@ -364,6 +409,26 @@
     }
 }
 
+@Composable
+private fun LockStateIcon(
+    isUnlocked: Boolean,
+    modifier: Modifier = Modifier,
+) {
+    val colors = LocalAndroidColorScheme.current
+    val resource =
+        if (isUnlocked) {
+            R.drawable.ic_unlocked
+        } else {
+            R.drawable.ic_lock
+        }
+    Icon(
+        painter = painterResource(id = resource),
+        contentDescription = null,
+        tint = colors.onPrimaryContainer,
+        modifier = modifier.size(Dimensions.LockIconSize),
+    )
+}
+
 /**
  * Toolbar that contains action buttons to
  * 1) open the widget picker
@@ -923,6 +988,9 @@
             horizontal = ToolbarButtonPaddingHorizontal,
         )
     val IconSize = 48.dp
+
+    val LockIconSize = 52.dp
+    val LockIconBottomPadding = 70.dp
 }
 
 private object Colors {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
index 0728daf..2a99039 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.keyguard.ui.viewmodel.BurnInScaleViewModel
+import kotlinx.coroutines.flow.map
 
 /**
  * Modifies the composable to account for anti-burn in translation, alpha, and scaling.
@@ -38,9 +39,18 @@
     params: BurnInParameters,
     isClock: Boolean = false,
 ): Modifier {
-    val translationX by viewModel.translationX(params).collectAsState(initial = 0f)
-    val translationY by viewModel.translationY(params).collectAsState(initial = 0f)
-    val scaleViewModel by viewModel.scale(params).collectAsState(initial = BurnInScaleViewModel())
+    val burnIn = viewModel.movement(params)
+    val translationX by burnIn.map { it.translationX.toFloat() }.collectAsState(initial = 0f)
+    val translationY by burnIn.map { it.translationY.toFloat() }.collectAsState(initial = 0f)
+    val scaleViewModel by
+        burnIn
+            .map {
+                BurnInScaleViewModel(
+                    scale = it.scale,
+                    scaleClockOnly = it.scaleClockOnly,
+                )
+            }
+            .collectAsState(initial = BurnInScaleViewModel())
 
     return this.graphicsLayer {
         val scale =
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
new file mode 100644
index 0000000..ae267e2
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
@@ -0,0 +1,258 @@
+/*
+ * 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.selector.ui.composable
+
+import androidx.compose.animation.core.animateOffsetAsState
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+
+/**
+ * Radio button group for the Volume Panel. It allows selecting a single item
+ *
+ * @param indicatorBackgroundPadding is the distance between the edge of the indicator and the
+ *   indicator background
+ * @param labelIndicatorBackgroundSpacing is the distance between indicator background and labels
+ *   row
+ */
+@Composable
+fun VolumePanelRadioButtonBar(
+    modifier: Modifier = Modifier,
+    indicatorBackgroundPadding: Dp =
+        VolumePanelRadioButtonBarDefaults.DefaultIndicatorBackgroundPadding,
+    spacing: Dp = VolumePanelRadioButtonBarDefaults.DefaultSpacing,
+    labelIndicatorBackgroundSpacing: Dp =
+        VolumePanelRadioButtonBarDefaults.DefaultLabelIndicatorBackgroundSpacing,
+    indicatorCornerRadius: CornerRadius =
+        VolumePanelRadioButtonBarDefaults.defaultIndicatorCornerRadius(),
+    indicatorBackgroundCornerSize: CornerSize =
+        CornerSize(VolumePanelRadioButtonBarDefaults.DefaultIndicatorBackgroundCornerRadius),
+    colors: VolumePanelRadioButtonBarColors = VolumePanelRadioButtonBarDefaults.defaultColors(),
+    content: VolumePanelRadioButtonBarScope.() -> Unit
+) {
+    val scope =
+        VolumePanelRadioButtonBarScopeImpl().apply(content).apply {
+            require(hasSelectedItem) { "At least one item should be selected" }
+        }
+
+    val items = scope.items
+
+    var selectedIndex by remember { mutableIntStateOf(items.indexOfFirst { it.isSelected }) }
+
+    var size by remember { mutableStateOf(IntSize(0, 0)) }
+    val spacingPx = with(LocalDensity.current) { spacing.toPx() }
+    val indicatorWidth = size.width / items.size - (spacingPx * (items.size - 1) / items.size)
+    val offset by
+        animateOffsetAsState(
+            targetValue =
+                Offset(
+                    selectedIndex * indicatorWidth + (spacingPx * selectedIndex),
+                    0f,
+                ),
+            label = "VolumePanelRadioButtonOffsetAnimation",
+            finishedListener = {
+                for (itemIndex in items.indices) {
+                    val item = items[itemIndex]
+                    if (itemIndex == selectedIndex) {
+                        item.onItemSelected()
+                        break
+                    }
+                }
+            }
+        )
+
+    Column(modifier = modifier) {
+        Box(modifier = Modifier.height(IntrinsicSize.Max)) {
+            Canvas(
+                modifier =
+                    Modifier.fillMaxSize()
+                        .background(
+                            colors.indicatorBackgroundColor,
+                            RoundedCornerShape(indicatorBackgroundCornerSize),
+                        )
+                        .padding(indicatorBackgroundPadding)
+                        .onGloballyPositioned { size = it.size }
+            ) {
+                drawRoundRect(
+                    color = colors.indicatorColor,
+                    topLeft = offset,
+                    size = Size(indicatorWidth, size.height.toFloat()),
+                    cornerRadius = indicatorCornerRadius,
+                )
+            }
+            Row(
+                modifier = Modifier.padding(indicatorBackgroundPadding),
+                horizontalArrangement = Arrangement.spacedBy(spacing)
+            ) {
+                for (itemIndex in items.indices) {
+                    TextButton(
+                        modifier = Modifier.weight(1f),
+                        onClick = { selectedIndex = itemIndex },
+                    ) {
+                        val item = items[itemIndex]
+                        if (item.icon !== Empty) {
+                            with(items[itemIndex]) { icon() }
+                        }
+                    }
+                }
+            }
+        }
+
+        Row(
+            modifier =
+                Modifier.padding(
+                    start = indicatorBackgroundPadding,
+                    top = labelIndicatorBackgroundSpacing,
+                    end = indicatorBackgroundPadding
+                ),
+            horizontalArrangement = Arrangement.spacedBy(spacing),
+        ) {
+            for (itemIndex in items.indices) {
+                TextButton(
+                    modifier = Modifier.weight(1f),
+                    onClick = { selectedIndex = itemIndex },
+                ) {
+                    val item = items[itemIndex]
+                    if (item.icon !== Empty) {
+                        with(items[itemIndex]) { label() }
+                    }
+                }
+            }
+        }
+    }
+}
+
+data class VolumePanelRadioButtonBarColors(
+    /** Color of the indicator. */
+    val indicatorColor: Color,
+    /** Color of the indicator background. */
+    val indicatorBackgroundColor: Color,
+)
+
+object VolumePanelRadioButtonBarDefaults {
+
+    val DefaultIndicatorBackgroundPadding = 8.dp
+    val DefaultSpacing = 24.dp
+    val DefaultLabelIndicatorBackgroundSpacing = 12.dp
+    val DefaultIndicatorCornerRadius = 20.dp
+    val DefaultIndicatorBackgroundCornerRadius = 20.dp
+
+    @Composable
+    fun defaultIndicatorCornerRadius(
+        x: Dp = DefaultIndicatorCornerRadius,
+        y: Dp = DefaultIndicatorCornerRadius,
+    ): CornerRadius = with(LocalDensity.current) { CornerRadius(x.toPx(), y.toPx()) }
+
+    /**
+     * Returns the default VolumePanelRadioButtonBar colors.
+     *
+     * @param indicatorColor is the color of the indicator
+     * @param indicatorBackgroundColor is the color of the indicator background
+     */
+    @Composable
+    fun defaultColors(
+        indicatorColor: Color = MaterialTheme.colorScheme.primaryContainer,
+        indicatorBackgroundColor: Color = MaterialTheme.colorScheme.surface,
+    ): VolumePanelRadioButtonBarColors =
+        VolumePanelRadioButtonBarColors(
+            indicatorColor = indicatorColor,
+            indicatorBackgroundColor = indicatorBackgroundColor,
+        )
+}
+
+/** [VolumePanelRadioButtonBar] content scope. Use [item] to add more items. */
+interface VolumePanelRadioButtonBarScope {
+
+    /**
+     * Adds a single item to the radio button group.
+     *
+     * @param isSelected true when the item is selected and false the otherwise
+     * @param onItemSelected is called when the item is selected
+     * @param icon of the to show in the indicator bar
+     * @param label to show below the indicator bar for the corresponding [icon]
+     */
+    fun item(
+        isSelected: Boolean,
+        onItemSelected: () -> Unit,
+        icon: @Composable RowScope.() -> Unit = Empty,
+        label: @Composable RowScope.() -> Unit = Empty,
+    )
+}
+
+private val Empty: @Composable RowScope.() -> Unit = {}
+
+private class VolumePanelRadioButtonBarScopeImpl : VolumePanelRadioButtonBarScope {
+
+    var hasSelectedItem: Boolean = false
+        private set
+
+    private val mutableItems: MutableList<Item> = mutableListOf()
+    val items: List<Item> = mutableItems
+
+    override fun item(
+        isSelected: Boolean,
+        onItemSelected: () -> Unit,
+        icon: @Composable RowScope.() -> Unit,
+        label: @Composable RowScope.() -> Unit,
+    ) {
+        require(!isSelected || !hasSelectedItem) { "Only one item should be selected at a time" }
+        hasSelectedItem = hasSelectedItem || isSelected
+        mutableItems.add(
+            Item(
+                isSelected = isSelected,
+                onItemSelected = onItemSelected,
+                icon = icon,
+                label = label,
+            )
+        )
+    }
+}
+
+private class Item(
+    val isSelected: Boolean,
+    val onItemSelected: () -> Unit,
+    val icon: @Composable RowScope.() -> Unit,
+    val label: @Composable RowScope.() -> Unit,
+)
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
new file mode 100644
index 0000000..da29d58
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.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.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
+
+/** Dagger module, that provides Spatial Audio Volume Panel UI functionality. */
+@Module
+interface SpatialAudioModule {
+
+    @Binds
+    @IntoMap
+    @StringKey(VolumePanelComponents.SPATIAL_AUDIO)
+    fun bindComponentAvailabilityCriteria(
+        criteria: SpatialAudioAvailabilityCriteria
+    ): ComponentAvailabilityCriteria
+
+    companion object {
+
+        @Provides
+        @IntoMap
+        @StringKey(VolumePanelComponents.SPATIAL_AUDIO)
+        fun provideVolumePanelUiComponent(
+            viewModel: SpatialAudioViewModel,
+            popup: SpatialAudioPopup,
+        ): VolumePanelUiComponent = ButtonComponent(viewModel.spatialAudioButton, popup::show)
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
new file mode 100644
index 0000000..bed0ae8
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.common.ui.compose.toColor
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup
+import com.android.systemui.volume.panel.component.selector.ui.composable.VolumePanelRadioButtonBar
+import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel
+import javax.inject.Inject
+
+class SpatialAudioPopup
+@Inject
+constructor(
+    private val viewModel: SpatialAudioViewModel,
+    private val volumePanelPopup: VolumePanelPopup,
+) {
+
+    /** Shows a popup with the [expandable] animation. */
+    fun show(expandable: Expandable) {
+        volumePanelPopup.show(expandable, { Title() }, { Content(it) })
+    }
+
+    @Composable
+    private fun Title() {
+        Text(
+            text = stringResource(R.string.volume_panel_spatial_audio_title),
+            style = MaterialTheme.typography.titleMedium,
+            textAlign = TextAlign.Center,
+            maxLines = 1,
+        )
+    }
+
+    @Composable
+    private fun Content(dialog: SystemUIDialog) {
+        val isAvailable by viewModel.isAvailable.collectAsState()
+
+        if (!isAvailable) {
+            SideEffect { dialog.dismiss() }
+            return
+        }
+
+        val enabledModelStates by viewModel.spatialAudioButtonByEnabled.collectAsState()
+        if (enabledModelStates.isEmpty()) {
+            return
+        }
+        VolumePanelRadioButtonBar {
+            for (buttonViewModel in enabledModelStates) {
+                item(
+                    isSelected = buttonViewModel.button.isChecked,
+                    onItemSelected = { viewModel.setEnabled(buttonViewModel.model) },
+                    icon = {
+                        Icon(
+                            icon = buttonViewModel.button.icon,
+                            tint = buttonViewModel.iconColor.toColor(),
+                        )
+                    },
+                    label = {
+                        Text(
+                            text = buttonViewModel.button.label.toString(),
+                            style = MaterialTheme.typography.labelMedium,
+                            color = buttonViewModel.labelColor.toColor(),
+                        )
+                    }
+                )
+            }
+        }
+    }
+}
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 563aad1..8f802b8 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
@@ -39,6 +39,7 @@
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -113,6 +114,7 @@
                 kosmos.communalInteractor,
                 kosmos.communalTutorialInteractor,
                 kosmos.shadeInteractor,
+                kosmos.deviceEntryInteractor,
                 mediaHost,
                 logcatLogBuffer("CommunalViewModelTest"),
             )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
index 0a3aea7..723f6a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
@@ -27,6 +27,8 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.wakelock.WakeLockFake
+import com.google.common.truth.Truth.assertThat
 import java.util.Optional
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -44,6 +46,9 @@
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
 
+    private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
+    private lateinit var fakeWakeLock: WakeLockFake
+
     @Mock private lateinit var taskFragmentComponentFactory: TaskFragmentComponent.Factory
     @Mock private lateinit var taskFragmentComponent: TaskFragmentComponent
     @Mock private lateinit var activity: Activity
@@ -57,6 +62,10 @@
             whenever(taskFragmentComponentFactory.create(any(), any(), any(), any()))
                 .thenReturn(taskFragmentComponent)
 
+            fakeWakeLock = WakeLockFake()
+            fakeWakeLockBuilder = WakeLockFake.Builder(context)
+            fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
+
             whenever(controlsComponent.getControlsListingController())
                 .thenReturn(Optional.of(controlsListingController))
 
@@ -87,12 +96,29 @@
             verify(taskFragmentComponentFactory, never()).create(any(), any(), any(), any())
         }
 
+    @Test
+    fun testAttachWindow_wakeLockAcquired() =
+        testScope.runTest {
+            underTest.onAttachedToWindow()
+            assertThat(fakeWakeLock.isHeld).isTrue()
+        }
+    @Test
+    fun testDetachWindow_wakeLockCanBeReleased() =
+        testScope.runTest {
+            underTest.onAttachedToWindow()
+            assertThat(fakeWakeLock.isHeld).isTrue()
+
+            underTest.onDetachedFromWindow()
+            assertThat(fakeWakeLock.isHeld).isFalse()
+        }
+
     private fun buildService(activityProvider: DreamActivityProvider): HomeControlsDreamService =
         with(kosmos) {
             return HomeControlsDreamService(
                 controlsSettingsRepository = FakeControlsSettingsRepository(),
                 taskFragmentFactory = taskFragmentComponentFactory,
                 homeControlsComponentInteractor = homeControlsComponentInteractor,
+                fakeWakeLockBuilder,
                 dreamActivityProvider = activityProvider,
                 bgDispatcher = testDispatcher,
                 logBuffer = logcatLogBuffer("HomeControlsDreamServiceTest")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 128b465..19b80da 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.common.shared.model.Position
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.doze.DozeMachine
 import com.android.systemui.doze.DozeTransitionCallback
@@ -152,24 +151,6 @@
         }
 
     @Test
-    fun clockPosition() =
-        testScope.runTest {
-            assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
-
-            underTest.setClockPosition(0, 1)
-            assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1))
-
-            underTest.setClockPosition(1, 9)
-            assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9))
-
-            underTest.setClockPosition(1, 0)
-            assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0))
-
-            underTest.setClockPosition(3, 1)
-            assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1))
-        }
-
-    @Test
     fun dozeTimeTick() =
         testScope.runTest {
             val lastDozeTimeTick by collectLastValue(underTest.dozeTimeTick)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelTest.kt
new file mode 100644
index 0000000..87d1cd5
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelTest.kt
@@ -0,0 +1,161 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import android.hardware.fingerprint.FingerprintManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.shared.model.FaceFailureMessage
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FingerprintFailureMessage
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AlternateBouncerMessageAreaViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val fingerprintAuthRepository by lazy {
+        kosmos.fakeDeviceEntryFingerprintAuthRepository
+    }
+    private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository }
+    private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
+    private val biometricSettingsRepository by lazy { kosmos.fakeBiometricSettingsRepository }
+    private val underTest: AlternateBouncerMessageAreaViewModel =
+        kosmos.alternateBouncerMessageAreaViewModel
+
+    @Before
+    fun setUp() {
+        biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+        biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
+    }
+
+    @Test
+    fun noInitialValue() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+            bouncerRepository.setAlternateVisible(true)
+            assertThat(message).isNull()
+        }
+
+    @Test
+    fun fingerprintMessage() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+            bouncerRepository.setAlternateVisible(true)
+            runCurrent()
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+            assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java)
+        }
+
+    @Test
+    fun fingerprintLockoutMessage_notShown() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+            bouncerRepository.setAlternateVisible(true)
+            runCurrent()
+            fingerprintAuthRepository.setAuthenticationStatus(
+                ErrorFingerprintAuthenticationStatus(
+                    msgId = FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+                    msg = "test lockout",
+                )
+            )
+            assertThat(message).isNull()
+        }
+
+    @Test
+    fun alternateBouncerNotVisible_messagesNeverShow() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+            bouncerRepository.setAlternateVisible(false)
+            runCurrent()
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+            assertThat(message).isNull()
+
+            faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus())
+            assertThat(message).isNull()
+        }
+
+    @Test
+    fun faceFailMessage() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+            bouncerRepository.setAlternateVisible(true)
+            runCurrent()
+            faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus())
+            assertThat(message).isInstanceOf(FaceFailureMessage::class.java)
+        }
+
+    @Test
+    fun faceThenFingerprintMessage() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+            bouncerRepository.setAlternateVisible(true)
+            runCurrent()
+            faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus())
+            assertThat(message).isInstanceOf(FaceFailureMessage::class.java)
+
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+            assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java)
+        }
+
+    @Test
+    fun fingerprintMessagePreventsFaceMessageFromShowing() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+            bouncerRepository.setAlternateVisible(true)
+            runCurrent()
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+            assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java)
+
+            faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus())
+            assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java)
+        }
+
+    @Test
+    fun fingerprintMessageAllowsFaceMessageAfter4000ms() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+            bouncerRepository.setAlternateVisible(true)
+            runCurrent()
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+            assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java)
+
+            advanceTimeBy(4000)
+
+            faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus())
+            assertThat(message).isInstanceOf(FaceFailureMessage::class.java)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index b0f59fe..de659cf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -71,7 +71,7 @@
         mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
 
         MockitoAnnotations.initMocks(this)
-        whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow)
+        whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow)
         kosmos.burnInInteractor = burnInInteractor
         whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt()))
             .thenReturn(emptyFlow())
@@ -81,18 +81,18 @@
     }
 
     @Test
-    fun translationY_initializedToZero() =
+    fun movement_initializedToZero() =
         testScope.runTest {
-            val translationY by collectLastValue(underTest.translationY(burnInParameters))
-            assertThat(translationY).isEqualTo(0)
+            val movement by collectLastValue(underTest.movement(burnInParameters))
+            assertThat(movement?.translationY).isEqualTo(0)
+            assertThat(movement?.translationX).isEqualTo(0)
+            assertThat(movement?.scale).isEqualTo(0f)
         }
 
     @Test
     fun translationAndScale_whenNotDozing() =
         testScope.runTest {
-            val translationX by collectLastValue(underTest.translationX(burnInParameters))
-            val translationY by collectLastValue(underTest.translationY(burnInParameters))
-            val scale by collectLastValue(underTest.scale(burnInParameters))
+            val movement by collectLastValue(underTest.movement(burnInParameters))
 
             // Set to not dozing (on lockscreen)
             keyguardTransitionRepository.sendTransitionStep(
@@ -113,24 +113,17 @@
                     scale = 0.5f,
                 )
 
-            assertThat(translationX).isEqualTo(0)
-            assertThat(translationY).isEqualTo(0)
-            assertThat(scale)
-                .isEqualTo(
-                    BurnInScaleViewModel(
-                        scale = 1f,
-                        scaleClockOnly = true,
-                    )
-                )
+            assertThat(movement?.translationX).isEqualTo(0)
+            assertThat(movement?.translationY).isEqualTo(0)
+            assertThat(movement?.scale).isEqualTo(1f)
+            assertThat(movement?.scaleClockOnly).isEqualTo(true)
         }
 
     @Test
     fun translationAndScale_whenFullyDozing() =
         testScope.runTest {
             burnInParameters = burnInParameters.copy(minViewY = 100)
-            val translationX by collectLastValue(underTest.translationX(burnInParameters))
-            val translationY by collectLastValue(underTest.translationY(burnInParameters))
-            val scale by collectLastValue(underTest.scale(burnInParameters))
+            val movement by collectLastValue(underTest.movement(burnInParameters))
 
             // Set to dozing (on AOD)
             keyguardTransitionRepository.sendTransitionStep(
@@ -150,15 +143,10 @@
                     scale = 0.5f,
                 )
 
-            assertThat(translationX).isEqualTo(20)
-            assertThat(translationY).isEqualTo(30)
-            assertThat(scale)
-                .isEqualTo(
-                    BurnInScaleViewModel(
-                        scale = 0.5f,
-                        scaleClockOnly = true,
-                    )
-                )
+            assertThat(movement?.translationX).isEqualTo(20)
+            assertThat(movement?.translationY).isEqualTo(30)
+            assertThat(movement?.scale).isEqualTo(0.5f)
+            assertThat(movement?.scaleClockOnly).isEqualTo(true)
 
             // Set to the beginning of GONE->AOD transition
             keyguardTransitionRepository.sendTransitionStep(
@@ -170,15 +158,10 @@
                 ),
                 validateStep = false,
             )
-            assertThat(translationX).isEqualTo(0)
-            assertThat(translationY).isEqualTo(0)
-            assertThat(scale)
-                .isEqualTo(
-                    BurnInScaleViewModel(
-                        scale = 1f,
-                        scaleClockOnly = true,
-                    )
-                )
+            assertThat(movement?.translationX).isEqualTo(0)
+            assertThat(movement?.translationY).isEqualTo(0)
+            assertThat(movement?.scale).isEqualTo(1f)
+            assertThat(movement?.scaleClockOnly).isEqualTo(true)
         }
 
     @Test
@@ -191,9 +174,7 @@
                     minViewY = 100,
                     topInset = 80,
                 )
-            val translationX by collectLastValue(underTest.translationX(burnInParameters))
-            val translationY by collectLastValue(underTest.translationY(burnInParameters))
-            val scale by collectLastValue(underTest.scale(burnInParameters))
+            val movement by collectLastValue(underTest.movement(burnInParameters))
 
             // Set to dozing (on AOD)
             keyguardTransitionRepository.sendTransitionStep(
@@ -213,16 +194,11 @@
                     translationY = -30,
                     scale = 0.5f,
                 )
-            assertThat(translationX).isEqualTo(20)
+            assertThat(movement?.translationX).isEqualTo(20)
             // -20 instead of -30, due to inset of 80
-            assertThat(translationY).isEqualTo(-20)
-            assertThat(scale)
-                .isEqualTo(
-                    BurnInScaleViewModel(
-                        scale = 0.5f,
-                        scaleClockOnly = true,
-                    )
-                )
+            assertThat(movement?.translationY).isEqualTo(-20)
+            assertThat(movement?.scale).isEqualTo(0.5f)
+            assertThat(movement?.scaleClockOnly).isEqualTo(true)
 
             // Set to the beginning of GONE->AOD transition
             keyguardTransitionRepository.sendTransitionStep(
@@ -234,15 +210,10 @@
                 ),
                 validateStep = false,
             )
-            assertThat(translationX).isEqualTo(0)
-            assertThat(translationY).isEqualTo(0)
-            assertThat(scale)
-                .isEqualTo(
-                    BurnInScaleViewModel(
-                        scale = 1f,
-                        scaleClockOnly = true,
-                    )
-                )
+            assertThat(movement?.translationX).isEqualTo(0)
+            assertThat(movement?.translationY).isEqualTo(0)
+            assertThat(movement?.scale).isEqualTo(1f)
+            assertThat(movement?.scaleClockOnly).isEqualTo(true)
         }
 
     @Test
@@ -255,9 +226,7 @@
                     minViewY = 100,
                     topInset = 80,
                 )
-            val translationX by collectLastValue(underTest.translationX(burnInParameters))
-            val translationY by collectLastValue(underTest.translationY(burnInParameters))
-            val scale by collectLastValue(underTest.scale(burnInParameters))
+            val movement by collectLastValue(underTest.movement(burnInParameters))
 
             // Set to dozing (on AOD)
             keyguardTransitionRepository.sendTransitionStep(
@@ -277,16 +246,11 @@
                     translationY = -30,
                     scale = 0.5f,
                 )
-            assertThat(translationX).isEqualTo(20)
+            assertThat(movement?.translationX).isEqualTo(20)
             // -20 instead of -30, due to inset of 80
-            assertThat(translationY).isEqualTo(-20)
-            assertThat(scale)
-                .isEqualTo(
-                    BurnInScaleViewModel(
-                        scale = 0.5f,
-                        scaleClockOnly = true,
-                    )
-                )
+            assertThat(movement?.translationY).isEqualTo(-20)
+            assertThat(movement?.scale).isEqualTo(0.5f)
+            assertThat(movement?.scaleClockOnly).isEqualTo(true)
 
             // Set to the beginning of GONE->AOD transition
             keyguardTransitionRepository.sendTransitionStep(
@@ -298,15 +262,10 @@
                 ),
                 validateStep = false,
             )
-            assertThat(translationX).isEqualTo(0)
-            assertThat(translationY).isEqualTo(0)
-            assertThat(scale)
-                .isEqualTo(
-                    BurnInScaleViewModel(
-                        scale = 1f,
-                        scaleClockOnly = true,
-                    )
-                )
+            assertThat(movement?.translationX).isEqualTo(0)
+            assertThat(movement?.translationY).isEqualTo(0)
+            assertThat(movement?.scale).isEqualTo(1f)
+            assertThat(movement?.scaleClockOnly).isEqualTo(true)
         }
 
     @Test
@@ -314,9 +273,7 @@
         testScope.runTest {
             whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true)
 
-            val translationX by collectLastValue(underTest.translationX(burnInParameters))
-            val translationY by collectLastValue(underTest.translationY(burnInParameters))
-            val scale by collectLastValue(underTest.scale(burnInParameters))
+            val movement by collectLastValue(underTest.movement(burnInParameters))
 
             // Set to dozing (on AOD)
             keyguardTransitionRepository.sendTransitionStep(
@@ -337,8 +294,9 @@
                     scale = 0.5f,
                 )
 
-            assertThat(translationX).isEqualTo(0)
-            assertThat(translationY).isEqualTo(0)
-            assertThat(scale).isEqualTo(BurnInScaleViewModel(scale = 0.5f, scaleClockOnly = false))
+            assertThat(movement?.translationX).isEqualTo(0)
+            assertThat(movement?.translationY).isEqualTo(0)
+            assertThat(movement?.scale).isEqualTo(0.5f)
+            assertThat(movement?.scaleClockOnly).isEqualTo(false)
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
new file mode 100644
index 0000000..04c270d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
@@ -0,0 +1,186 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags as AConfigFlags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
+    @Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
+
+    @Mock private lateinit var burnInInteractor: BurnInInteractor
+    private val burnInFlow = MutableStateFlow(BurnInModel())
+
+    private lateinit var bottomAreaInteractor: KeyguardBottomAreaInteractor
+    private lateinit var underTest: KeyguardIndicationAreaViewModel
+    private lateinit var repository: FakeKeyguardRepository
+
+    private val startButtonFlow =
+        MutableStateFlow<KeyguardQuickAffordanceViewModel>(
+            KeyguardQuickAffordanceViewModel(
+                slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()
+            )
+        )
+    private val endButtonFlow =
+        MutableStateFlow<KeyguardQuickAffordanceViewModel>(
+            KeyguardQuickAffordanceViewModel(
+                slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId()
+            )
+        )
+    private val alphaFlow = MutableStateFlow<Float>(1f)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
+        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+
+        whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
+            .thenReturn(RETURNED_BURN_IN_OFFSET)
+        whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow)
+
+        val withDeps = KeyguardInteractorFactory.create()
+        val keyguardInteractor = withDeps.keyguardInteractor
+        repository = withDeps.repository
+
+        val bottomAreaViewModel: KeyguardBottomAreaViewModel = mock()
+        whenever(bottomAreaViewModel.startButton).thenReturn(startButtonFlow)
+        whenever(bottomAreaViewModel.endButton).thenReturn(endButtonFlow)
+        whenever(bottomAreaViewModel.alpha).thenReturn(alphaFlow)
+        bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository)
+        underTest =
+            KeyguardIndicationAreaViewModel(
+                keyguardInteractor = keyguardInteractor,
+                bottomAreaInteractor = bottomAreaInteractor,
+                keyguardBottomAreaViewModel = bottomAreaViewModel,
+                burnInHelperWrapper = burnInHelperWrapper,
+                burnInInteractor = burnInInteractor,
+                shortcutsCombinedViewModel = shortcutsCombinedViewModel,
+                configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
+            )
+    }
+
+    @Test
+    fun alpha() =
+        testScope.runTest {
+            val value = collectLastValue(underTest.alpha)
+
+            assertThat(value()).isEqualTo(1f)
+            alphaFlow.value = 0.1f
+            assertThat(value()).isEqualTo(0.1f)
+            alphaFlow.value = 0.5f
+            assertThat(value()).isEqualTo(0.5f)
+            alphaFlow.value = 0.2f
+            assertThat(value()).isEqualTo(0.2f)
+            alphaFlow.value = 0f
+            assertThat(value()).isEqualTo(0f)
+        }
+
+    @Test
+    fun isIndicationAreaPadded() =
+        testScope.runTest {
+            repository.setKeyguardShowing(true)
+            val value = collectLastValue(underTest.isIndicationAreaPadded)
+
+            assertThat(value()).isFalse()
+            startButtonFlow.value = startButtonFlow.value.copy(isVisible = true)
+            assertThat(value()).isTrue()
+            endButtonFlow.value = endButtonFlow.value.copy(isVisible = true)
+            assertThat(value()).isTrue()
+            startButtonFlow.value = startButtonFlow.value.copy(isVisible = false)
+            assertThat(value()).isTrue()
+            endButtonFlow.value = endButtonFlow.value.copy(isVisible = false)
+            assertThat(value()).isFalse()
+        }
+
+    @Test
+    fun indicationAreaTranslationX() =
+        testScope.runTest {
+            val value = collectLastValue(underTest.indicationAreaTranslationX)
+
+            assertThat(value()).isEqualTo(0f)
+            bottomAreaInteractor.setClockPosition(100, 100)
+            assertThat(value()).isEqualTo(100f)
+            bottomAreaInteractor.setClockPosition(200, 100)
+            assertThat(value()).isEqualTo(200f)
+            bottomAreaInteractor.setClockPosition(200, 200)
+            assertThat(value()).isEqualTo(200f)
+            bottomAreaInteractor.setClockPosition(300, 100)
+            assertThat(value()).isEqualTo(300f)
+        }
+
+    @Test
+    fun indicationAreaTranslationY() =
+        testScope.runTest {
+            val value =
+                collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET))
+
+            // Negative 0 - apparently there's a difference in floating point arithmetic - FML
+            assertThat(value()).isEqualTo(-0f)
+            val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f)
+            assertThat(value()).isEqualTo(expected1)
+            val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f)
+            assertThat(value()).isEqualTo(expected2)
+            val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f)
+            assertThat(value()).isEqualTo(expected3)
+            val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f)
+            assertThat(value()).isEqualTo(expected4)
+        }
+
+    private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
+        repository.setDozeAmount(dozeAmount)
+        return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
+    }
+
+    companion object {
+        private const val DEFAULT_BURN_IN_OFFSET = 5
+        private const val RETURNED_BURN_IN_OFFSET = 3
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index d505b27..7fabe33 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -25,6 +25,7 @@
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
 import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS;
+import static android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES;
 import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
 import static android.os.UserHandle.USER_ALL;
 import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
@@ -115,7 +116,8 @@
     public static List<FlagsParameterization> getParams() {
         return FlagsParameterization.allCombinationsOf(
                 FLAG_ALLOW_PRIVATE_PROFILE,
-                FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS);
+                FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS,
+                FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
     }
 
     public NotificationLockscreenUserManagerTest(FlagsParameterization flags) {
@@ -872,7 +874,7 @@
     }
 
     @Test
-    @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+    @EnableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
     public void testProfileAvailabilityIntent() {
         mLockscreenUserManager.mCurrentProfiles.clear();
         assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
@@ -883,7 +885,7 @@
     }
 
     @Test
-    @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+    @EnableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
     public void testProfileUnAvailabilityIntent() {
         mLockscreenUserManager.mCurrentProfiles.clear();
         assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
@@ -894,7 +896,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+    @DisableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
     public void testManagedProfileAvailabilityIntent() {
         mLockscreenUserManager.mCurrentProfiles.clear();
         mLockscreenUserManager.mCurrentManagedProfiles.clear();
@@ -908,7 +910,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+    @DisableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
     public void testManagedProfileUnAvailabilityIntent() {
         mLockscreenUserManager.mCurrentProfiles.clear();
         mLockscreenUserManager.mCurrentManagedProfiles.clear();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt
index e188f5b..8e765f7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt
@@ -196,10 +196,14 @@
     }
 
     @Test
-    fun zenModeAlarms_ringAndNotifications_muted() {
+    fun zenModeAlarms_ringedStreams_muted() {
         with(kosmos) {
             val expectedToBeMuted =
-                setOf(AudioManager.STREAM_RING, AudioManager.STREAM_NOTIFICATION)
+                setOf(
+                    AudioManager.STREAM_RING,
+                    AudioManager.STREAM_NOTIFICATION,
+                    AudioManager.STREAM_SYSTEM,
+                )
             testScope.runTest {
                 notificationsSoundPolicyRepository.updateNotificationPolicy()
                 notificationsSoundPolicyRepository.updateZenMode(ZenMode(Global.ZEN_MODE_ALARMS))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 0de15b8..deb1976 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.shared.model.BurnInModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -66,7 +67,7 @@
 @RunWith(AndroidJUnit4::class)
 class SharedNotificationContainerViewModelTest : SysuiTestCase() {
     val aodBurnInViewModel = mock(AodBurnInViewModel::class.java)
-    lateinit var translationYFlow: MutableStateFlow<Float>
+    lateinit var movementFlow: MutableStateFlow<BurnInModel>
 
     val kosmos =
         testKosmos().apply {
@@ -95,8 +96,8 @@
     @Before
     fun setUp() {
         overrideResource(R.bool.config_use_split_notification_shade, false)
-        translationYFlow = MutableStateFlow(0f)
-        whenever(aodBurnInViewModel.translationY(any())).thenReturn(translationYFlow)
+        movementFlow = MutableStateFlow(BurnInModel())
+        whenever(aodBurnInViewModel.movement(any())).thenReturn(movementFlow)
         underTest = kosmos.sharedNotificationContainerViewModel
     }
 
@@ -608,7 +609,7 @@
             showLockscreen()
             assertThat(translationY).isEqualTo(0)
 
-            translationYFlow.value = 150f
+            movementFlow.value = BurnInModel(translationY = 150)
             assertThat(translationY).isEqualTo(150f)
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
index 36be90e..449e8bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
@@ -78,7 +78,7 @@
         with(kosmos) {
             testScope.runTest {
                 localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
-                spatializerRepository.setIsHeadTrackingAvailable(false)
+                spatializerRepository.defaultHeadTrackingAvailable = false
                 spatializerRepository.defaultSpatialAudioAvailable = false
 
                 val isAvailable by collectLastValue(underTest.isAvailable())
@@ -94,7 +94,7 @@
         with(kosmos) {
             testScope.runTest {
                 localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
-                spatializerRepository.setIsHeadTrackingAvailable(false)
+                spatializerRepository.defaultHeadTrackingAvailable = false
                 spatializerRepository.defaultSpatialAudioAvailable = true
 
                 val isAvailable by collectLastValue(underTest.isAvailable())
@@ -110,7 +110,7 @@
         with(kosmos) {
             testScope.runTest {
                 localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
-                spatializerRepository.setIsHeadTrackingAvailable(true)
+                spatializerRepository.defaultHeadTrackingAvailable = true
                 spatializerRepository.defaultSpatialAudioAvailable = true
 
                 val isAvailable by collectLastValue(underTest.isAvailable())
@@ -125,7 +125,7 @@
     fun spatialAudio_headTracking_noDevice_unavailable() {
         with(kosmos) {
             testScope.runTest {
-                spatializerRepository.setIsHeadTrackingAvailable(true)
+                spatializerRepository.defaultHeadTrackingAvailable = true
                 spatializerRepository.defaultSpatialAudioAvailable = true
 
                 val isAvailable by collectLastValue(underTest.isAvailable())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
index eb6f0b2..06ae220 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
@@ -82,7 +82,7 @@
                 ),
                 true
             )
-            spatializerRepository.setIsHeadTrackingAvailable(true)
+            spatializerRepository.defaultHeadTrackingAvailable = true
 
             underTest =
                 SpatialAudioComponentInteractor(
diff --git a/packages/SystemUI/res/drawable/ic_head_tracking.xml b/packages/SystemUI/res/drawable/ic_head_tracking.xml
new file mode 100644
index 0000000..d4a44fd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_head_tracking.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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:tint="?attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M480,520Q414,520 367,473Q320,426 320,360Q320,294 367,247Q414,200 480,200Q546,200 593,247Q640,294 640,360Q640,426 593,473Q546,520 480,520ZM160,840L160,728Q160,695 177,666Q194,637 224,622Q275,596 339,578Q403,560 480,560Q557,560 621,578Q685,596 736,622Q766,637 783,666Q800,695 800,728L800,840L160,840ZM240,760L720,760L720,728Q720,717 714.5,708Q709,699 700,694Q664,676 607.5,658Q551,640 480,640Q409,640 352.5,658Q296,676 260,694Q251,699 245.5,708Q240,717 240,728L240,760ZM480,440Q513,440 536.5,416.5Q560,393 560,360Q560,327 536.5,303.5Q513,280 480,280Q447,280 423.5,303.5Q400,327 400,360Q400,393 423.5,416.5Q447,440 480,440ZM39,200L39,120Q56,120 70,113.5Q84,107 95,96Q106,85 112,71Q118,57 118,40L199,40Q199,73 186.5,102Q174,131 152,153Q130,175 101,187.5Q72,200 39,200ZM39,361L39,281Q90,281 133.5,262Q177,243 209,210Q241,177 260,133.5Q279,90 279,40L360,40Q360,106 335,164.5Q310,223 266,267Q222,311 164,336Q106,361 39,361ZM920,361Q854,361 795.5,336Q737,311 693,267Q649,223 624,164.5Q599,106 599,40L679,40Q679,90 698,133.5Q717,177 750,210Q783,243 826.5,262Q870,281 920,281L920,361ZM920,200Q887,200 858,187.5Q829,175 807,153Q785,131 772.5,102Q760,73 760,40L840,40Q840,57 846.5,71Q853,85 864,96Q875,107 889,113.5Q903,120 920,120L920,200ZM480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360ZM480,760L480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760L480,760L480,760Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_spatial_audio.xml b/packages/SystemUI/res/drawable/ic_spatial_audio.xml
new file mode 100644
index 0000000..0ee609a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_spatial_audio.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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:tint="?attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M920,401Q848,401 782,373.5Q716,346 665,295Q614,244 586.5,178Q559,112 559,40L639,40Q639,97 660,148Q681,199 721,239Q761,279 812,300.5Q863,322 920,322L920,401ZM920,242Q879,242 842.5,227Q806,212 777,183Q748,154 733,117.5Q718,81 718,40L797,40Q797,65 806.5,87.5Q816,110 833,127Q850,144 872.5,153Q895,162 920,162L920,242ZM400,520Q334,520 287,473Q240,426 240,360Q240,294 287,247Q334,200 400,200Q466,200 513,247Q560,294 560,360Q560,426 513,473Q466,520 400,520ZM80,840L80,728Q80,695 97,666Q114,637 144,622Q195,596 259,578Q323,560 400,560Q477,560 541,578Q605,596 656,622Q686,637 703,666Q720,695 720,728L720,840L80,840ZM160,760L640,760L640,728Q640,717 634.5,708Q629,699 620,694Q584,676 527.5,658Q471,640 400,640Q329,640 272.5,658Q216,676 180,694Q171,699 165.5,708Q160,717 160,728L160,760ZM400,440Q433,440 456.5,416.5Q480,393 480,360Q480,327 456.5,303.5Q433,280 400,280Q367,280 343.5,303.5Q320,327 320,360Q320,393 343.5,416.5Q367,440 400,440ZM400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360ZM400,760L400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760L400,760L400,760Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_spatial_audio_off.xml b/packages/SystemUI/res/drawable/ic_spatial_audio_off.xml
new file mode 100644
index 0000000..c7d3272
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_spatial_audio_off.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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:tint="?attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M750,550L806,494Q766,454 743.5,402.5Q721,351 721,294Q721,237 743.5,186Q766,135 806,95L750,37Q699,88 670,155Q641,222 641,294Q641,366 670,432.5Q699,499 750,550ZM862,436L918,380Q901,363 891,341Q881,319 881,294Q881,269 891,247Q901,225 918,208L862,151Q833,180 817,216Q801,252 801,293Q801,334 817,371Q833,408 862,436ZM400,520Q334,520 287,473Q240,426 240,360Q240,294 287,247Q334,200 400,200Q466,200 513,247Q560,294 560,360Q560,426 513,473Q466,520 400,520ZM80,840L80,728Q80,695 97,666Q114,637 144,622Q195,596 259,578Q323,560 400,560Q477,560 541,578Q605,596 656,622Q686,637 703,666Q720,695 720,728L720,840L80,840ZM160,760L640,760L640,728Q640,717 634.5,708Q629,699 620,694Q584,676 527.5,658Q471,640 400,640Q329,640 272.5,658Q216,676 180,694Q171,699 165.5,708Q160,717 160,728L160,760ZM400,440Q433,440 456.5,416.5Q480,393 480,360Q480,327 456.5,303.5Q433,280 400,280Q367,280 343.5,303.5Q320,327 320,360Q320,393 343.5,416.5Q367,440 400,440ZM400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360ZM400,760L400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760L400,760L400,760Z" />
+</vector>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 71ae0d7..035cfdc 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -223,6 +223,7 @@
     <item type="id" name="lock_icon" />
     <item type="id" name="lock_icon_bg" />
     <item type="id" name="burn_in_layer" />
+    <item type="id" name="burn_in_layer_empty_view" />
     <item type="id" name="communal_tutorial_indicator" />
     <item type="id" name="nssl_placeholder_barrier_bottom" />
     <item type="id" name="ambient_indication_container" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5dbdd18..f71c415 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1538,8 +1538,16 @@
     <string name="volume_stream_content_description_vibrate_a11y">%1$s. Tap to set to vibrate.</string>
     <string name="volume_stream_content_description_mute_a11y">%1$s. Tap to mute.</string>
 
-    <!-- Label for button to enabled/disable live caption [CHAR_LIMIT=30] -->
+    <!-- Label for button to enabled/disable active noise cancellation [CHAR_LIMIT=30] -->
     <string name="volume_panel_noise_control_title">Noise Control</string>
+    <!-- Label for button to enabled/disable spatial audio [CHAR_LIMIT=30] -->
+    <string name="volume_panel_spatial_audio_title">Spatial Audio</string>
+    <!-- Label for button to disable spatial audio [CHAR_LIMIT=20] -->
+    <string name="volume_panel_spatial_audio_off">Off</string>
+    <!-- Label for button to enabled spatial audio [CHAR_LIMIT=20] -->
+    <string name="volume_panel_spatial_audio_fixed">Fixed</string>
+    <!-- Label for button to enabled head tracking [CHAR_LIMIT=20] -->
+    <string name="volume_panel_spatial_audio_tracking">Head Tracking</string>
 
     <string name="volume_ringer_change">Tap to change ringer mode</string>
 
diff --git a/packages/SystemUI/res/xml/fileprovider.xml b/packages/SystemUI/res/xml/fileprovider.xml
index b67378e..71cc05d 100644
--- a/packages/SystemUI/res/xml/fileprovider.xml
+++ b/packages/SystemUI/res/xml/fileprovider.xml
@@ -19,4 +19,5 @@
     <cache-path name="leak" path="leak/"/>
     <external-path name="screenrecord" path="."/>
     <cache-path name="multi_user" path="multi_user/" />
-</paths>
\ No newline at end of file
+    <root-path name="traces" path="/data/local/traces"/>
+</paths>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 9421f15..c0ae4a1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -53,9 +53,6 @@
 import com.android.systemui.animation.ViewHierarchyAnimator;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.shared.model.TransitionState;
-import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.plugins.clocks.ClockController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.power.shared.model.ScreenPowerState;
@@ -104,7 +101,6 @@
     private final Rect mClipBounds = new Rect();
     private final KeyguardInteractor mKeyguardInteractor;
     private final PowerInteractor mPowerInteractor;
-    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     private final DozeParameters mDozeParameters;
 
     private View mStatusArea = null;
@@ -112,7 +108,6 @@
 
     private Boolean mSplitShadeEnabled = false;
     private Boolean mStatusViewCentered = true;
-    private boolean mGoneToAodTransitionRunning = false;
     private DumpManager mDumpManager;
 
     private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
@@ -181,7 +176,6 @@
             KeyguardLogger logger,
             InteractionJankMonitor interactionJankMonitor,
             KeyguardInteractor keyguardInteractor,
-            KeyguardTransitionInteractor keyguardTransitionInteractor,
             DumpManager dumpManager,
             PowerInteractor powerInteractor) {
         super(keyguardStatusView);
@@ -197,7 +191,6 @@
         mDumpManager = dumpManager;
         mKeyguardInteractor = keyguardInteractor;
         mPowerInteractor = powerInteractor;
-        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
     }
 
     @Override
@@ -232,6 +225,7 @@
         mDumpManager.registerDumpable(getInstanceName(), this);
         if (migrateClocksToBlueprint()) {
             startCoroutines(EmptyCoroutineContext.INSTANCE);
+            mView.setVisibility(View.GONE);
         }
     }
 
@@ -247,15 +241,6 @@
                         dozeTimeTick();
                     }
                 }, context);
-
-        collectFlow(mView, mKeyguardTransitionInteractor.getGoneToAodTransition(),
-                (TransitionStep step) -> {
-                    if (step.getTransitionState() == TransitionState.RUNNING) {
-                        mGoneToAodTransitionRunning = true;
-                    } else {
-                        mGoneToAodTransitionRunning = false;
-                    }
-                }, context);
     }
 
     public KeyguardStatusView getView() {
@@ -326,7 +311,7 @@
      * Set keyguard status view alpha.
      */
     public void setAlpha(float alpha) {
-        if (!mKeyguardVisibilityHelper.isVisibilityAnimating() && !mGoneToAodTransitionRunning) {
+        if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
             mView.setAlpha(alpha);
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 2000028..f5a6cb3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -88,6 +88,10 @@
             boolean keyguardFadingAway,
             boolean goingToFullShade,
             int oldStatusBarState) {
+        if (migrateClocksToBlueprint()) {
+            log("Ignoring KeyguardVisibilityelper, migrateClocksToBlueprint flag on");
+            return;
+        }
         Assert.isMainThread();
         PropertyAnimator.cancelAnimation(mView, AnimatableProperty.ALPHA);
         boolean isOccluded = mKeyguardStateController.isOccluded();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 6299739..577bbc0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -22,6 +22,8 @@
 import android.content.ComponentCallbacks;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -435,7 +437,14 @@
         }
         mMenuAnimationController.flingMenuThenSpringToEdge(
                 getMenuPosition().x, 100f, 0f);
-        mContext.startActivity(getIntentForEditScreen());
+
+        Intent intent = getIntentForEditScreen();
+        PackageManager packageManager = getContext().getPackageManager();
+        List<ResolveInfo> activities = packageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY));
+        if (!activities.isEmpty()) {
+            mContext.startActivity(intent);
+        }
     }
 
     void incrementTexMetricForAllTargets(String metric) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index c64f666..12576d4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -152,4 +152,6 @@
     }
 
     fun isWidgetContent() = this is WidgetContent
+
+    fun isSmartspace() = this is Smartspace
 }
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 fc9a7df..35b27aa 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,6 +21,7 @@
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
@@ -46,6 +47,7 @@
 import kotlinx.coroutines.launch
 
 /** The default view model used for showing the communal hub. */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class CommunalViewModel
 @Inject
@@ -54,6 +56,7 @@
     private val communalInteractor: CommunalInteractor,
     tutorialInteractor: CommunalTutorialInteractor,
     shadeInteractor: ShadeInteractor,
+    deviceEntryInteractor: DeviceEntryInteractor,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
     @CommunalLog logBuffer: LogBuffer,
 ) : BaseCommunalViewModel(communalInteractor, mediaHost) {
@@ -87,6 +90,8 @@
     /** Whether touches should be disabled in communal */
     val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
 
+    val deviceUnlocked: Flow<Boolean> = deviceEntryInteractor.isUnlocked
+
     init {
         // Initialize our media host for the UMO. This only needs to happen once and must be done
         // before the MediaHierarchyManager attempts to move the UMO to the hub.
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
index e74814a..376d312 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.dreams.homecontrols
 
 import android.content.Intent
+import android.os.PowerManager
 import android.service.controls.ControlsProviderService
 import android.service.dreams.DreamService
 import android.window.TaskFragmentInfo
@@ -27,6 +28,8 @@
 import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor.Companion.MAX_UPDATE_CORRELATION_DELAY
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.dagger.DreamLog
+import com.android.systemui.util.wakelock.WakeLock
+import com.android.systemui.util.wakelock.WakeLock.Builder.NO_TIMEOUT
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
@@ -42,14 +45,23 @@
     private val controlsSettingsRepository: ControlsSettingsRepository,
     private val taskFragmentFactory: TaskFragmentComponent.Factory,
     private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
+    private val wakeLockBuilder: WakeLock.Builder,
     private val dreamActivityProvider: DreamActivityProvider,
     @Background private val bgDispatcher: CoroutineDispatcher,
     @DreamLog logBuffer: LogBuffer
 ) : DreamService() {
+
     private val serviceJob = SupervisorJob()
     private val serviceScope = CoroutineScope(bgDispatcher + serviceJob)
-    private val logger = DreamLogger(logBuffer, "HomeControlsDreamService")
+    private val logger = DreamLogger(logBuffer, TAG)
     private lateinit var taskFragmentComponent: TaskFragmentComponent
+    private val wakeLock: WakeLock by lazy {
+        wakeLockBuilder
+            .setMaxTimeout(NO_TIMEOUT)
+            .setTag(TAG)
+            .setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)
+            .build()
+    }
 
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
@@ -72,6 +84,8 @@
                     hide = { finish() }
                 )
                 .apply { createTaskFragment() }
+
+        wakeLock.acquire(TAG)
     }
 
     private fun onTaskFragmentInfoChanged(taskFragmentInfo: TaskFragmentInfo) {
@@ -100,6 +114,7 @@
 
     override fun onDetachedFromWindow() {
         super.onDetachedFromWindow()
+        wakeLock.release(TAG)
         taskFragmentComponent.destroy()
         serviceScope.launch {
             delay(CANCELLATION_DELAY_AFTER_DETACHED)
@@ -115,5 +130,6 @@
          * complete.
          */
         val CANCELLATION_DELAY_AFTER_DETACHED = 5.seconds
+        const val TAG = "HomeControlsDreamService"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 3a6423d..1298fa5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -24,7 +24,6 @@
 import com.android.systemui.biometrics.data.repository.FacePropertyRepository
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.common.shared.model.Position
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
@@ -80,12 +79,6 @@
     val keyguardAlpha: StateFlow<Float>
 
     /**
-     * Observable of the relative offset of the lock-screen clock from its natural position on the
-     * screen.
-     */
-    val clockPosition: StateFlow<Position>
-
-    /**
      * Observable for whether the keyguard is showing.
      *
      * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in
@@ -241,11 +234,6 @@
     fun setKeyguardAlpha(alpha: Float)
 
     /**
-     * Sets the relative offset of the lock-screen clock from its natural position on the screen.
-     */
-    fun setClockPosition(x: Int, y: Int)
-
-    /**
      * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
      */
     fun isUdfpsSupported(): Boolean
@@ -324,9 +312,6 @@
     private val _keyguardAlpha = MutableStateFlow(1f)
     override val keyguardAlpha = _keyguardAlpha.asStateFlow()
 
-    private val _clockPosition = MutableStateFlow(Position(0, 0))
-    override val clockPosition = _clockPosition.asStateFlow()
-
     private val _clockShouldBeCentered = MutableStateFlow(true)
     override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered.asStateFlow()
 
@@ -678,10 +663,6 @@
         _keyguardAlpha.value = alpha
     }
 
-    override fun setClockPosition(x: Int, y: Int) {
-        _clockPosition.value = Position(x, y)
-    }
-
     override fun isUdfpsSupported(): Boolean = keyguardUpdateMonitor.isUdfpsSupported
 
     override fun setQuickSettingsVisible(isVisible: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
index 7ae70a9..ca86289 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
@@ -19,7 +19,7 @@
 
 import android.content.Context
 import androidx.annotation.DimenRes
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.doze.util.BurnInHelperWrapper
@@ -47,13 +47,15 @@
     private val context: Context,
     private val burnInHelperWrapper: BurnInHelperWrapper,
     @Application private val scope: CoroutineScope,
-    private val configurationRepository: ConfigurationRepository,
+    private val configurationInteractor: ConfigurationInteractor,
     private val keyguardInteractor: KeyguardInteractor,
 ) {
     val deviceEntryIconXOffset: StateFlow<Int> =
         burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_x, isXAxis = true)
+            .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
     val deviceEntryIconYOffset: StateFlow<Int> =
         burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_y, isXAxis = false)
+            .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
     val udfpsProgress: StateFlow<Float> =
         keyguardInteractor.dozeTimeTick
             .mapLatest { burnInHelperWrapper.burnInProgressOffset() }
@@ -63,18 +65,18 @@
                 burnInHelperWrapper.burnInProgressOffset()
             )
 
-    val keyguardBurnIn: Flow<BurnInModel> =
-        combine(
-                burnInOffset(R.dimen.burn_in_prevention_offset_x, isXAxis = true),
-                burnInOffset(R.dimen.burn_in_prevention_offset_y, isXAxis = false).map {
-                    it * 2 -
-                        context.resources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y)
+    /** Given the max x,y dimens, determine the current translation shifts. */
+    fun burnIn(xDimenResourceId: Int, yDimenResourceId: Int): Flow<BurnInModel> {
+        return combine(
+                burnInOffset(xDimenResourceId, isXAxis = true),
+                burnInOffset(yDimenResourceId, isXAxis = false).map {
+                    it * 2 - context.resources.getDimensionPixelSize(yDimenResourceId)
                 }
             ) { translationX, translationY ->
                 BurnInModel(translationX, translationY, burnInHelperWrapper.burnInScale())
             }
             .distinctUntilChanged()
-            .stateIn(scope, SharingStarted.Lazily, BurnInModel())
+    }
 
     /**
      * Use for max burn-in offsets that are NOT specified in pixels. This flow will recalculate the
@@ -84,23 +86,14 @@
     private fun burnInOffset(
         @DimenRes maxBurnInOffsetResourceId: Int,
         isXAxis: Boolean,
-    ): StateFlow<Int> {
-        return configurationRepository.onAnyConfigurationChange
-            .flatMapLatest {
-                val maxBurnInOffsetPixels =
-                    context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
-                keyguardInteractor.dozeTimeTick.mapLatest {
-                    calculateOffset(maxBurnInOffsetPixels, isXAxis)
-                }
+    ): Flow<Int> {
+        return configurationInteractor.onAnyConfigurationChange.flatMapLatest {
+            val maxBurnInOffsetPixels =
+                context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
+            keyguardInteractor.dozeTimeTick.mapLatest {
+                calculateOffset(maxBurnInOffsetPixels, isXAxis)
             }
-            .stateIn(
-                scope,
-                SharingStarted.Lazily,
-                calculateOffset(
-                    context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId),
-                    isXAxis,
-                )
-            )
+        }
     }
 
     /**
@@ -111,24 +104,14 @@
     private fun burnInOffsetDefinedInPixels(
         @DimenRes maxBurnInOffsetResourceId: Int,
         isXAxis: Boolean,
-    ): StateFlow<Int> {
-        return configurationRepository.scaleForResolution
-            .flatMapLatest { scale ->
-                val maxBurnInOffsetPixels =
-                    context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
-                keyguardInteractor.dozeTimeTick.mapLatest {
-                    calculateOffset(maxBurnInOffsetPixels, isXAxis, scale)
-                }
+    ): Flow<Int> {
+        return configurationInteractor.scaleForResolution.flatMapLatest { scale ->
+            val maxBurnInOffsetPixels =
+                context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
+            keyguardInteractor.dozeTimeTick.mapLatest {
+                calculateOffset(maxBurnInOffsetPixels, isXAxis, scale)
             }
-            .stateIn(
-                scope,
-                SharingStarted.WhileSubscribed(),
-                calculateOffset(
-                    context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId),
-                    isXAxis,
-                    configurationRepository.getResolutionScale(),
-                )
-            )
+        }
     }
 
     private fun calculateOffset(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index bcad332..12b27eb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -357,7 +357,8 @@
                 from = KeyguardState.LOCKSCREEN,
                 modeOnCanceledFromStartedStep = { startedStep ->
                     if (
-                        startedStep.to == KeyguardState.AOD && startedStep.from == KeyguardState.AOD
+                        transitionInteractor.asleepKeyguardState.value == KeyguardState.AOD &&
+                            startedStep.from == KeyguardState.AOD
                     ) {
                         TransitionModeOnCanceled.REVERSE
                     } else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
index d2a7486..b9ec58c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
 
 /** Encapsulates business-logic specifically related to the keyguard bottom area. */
 @SysUISingleton
@@ -35,10 +37,13 @@
     /** The amount of alpha for the UI components of the bottom area. */
     val alpha: Flow<Float> = repository.bottomAreaAlpha
     /** The position of the keyguard clock. */
-    val clockPosition: Flow<Position> = repository.clockPosition
+    private val _clockPosition = MutableStateFlow(Position(0, 0))
+    /** See [ClockSection] */
+    @Deprecated("with migrateClocksToBlueprint()")
+    val clockPosition: Flow<Position> = _clockPosition.asStateFlow()
 
     fun setClockPosition(x: Int, y: Int) {
-        repository.setClockPosition(x, y)
+        _clockPosition.value = Position(x, y)
     }
 
     fun setAlpha(alpha: Float) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index f321bd7..143edf9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.common.shared.model.NotificationContainerBounds
-import com.android.systemui.common.shared.model.Position
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -37,6 +36,7 @@
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -235,9 +235,6 @@
     /** The approximate location on the screen of the face unlock sensor, if one is available. */
     val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
 
-    /** The position of the keyguard clock. */
-    val clockPosition: Flow<Position> = repository.clockPosition
-
     @Deprecated("Use the relevant TransitionViewModel")
     val keyguardAlpha: Flow<Float> = repository.keyguardAlpha
 
@@ -272,8 +269,11 @@
         configurationInteractor
             .dimensionPixelSize(R.dimen.keyguard_translate_distance_on_swipe_up)
             .flatMapLatest { translationDistance ->
-                shadeRepository.legacyShadeExpansion.map {
-                    if (it == 0f) {
+                combine(
+                    shadeRepository.legacyShadeExpansion.onStart { emit(0f) },
+                    keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
+                ) { legacyShadeExpansion, goneValue ->
+                    if (goneValue == 1f || legacyShadeExpansion == 0f) {
                         // Reset the translation value
                         0f
                     } else {
@@ -281,11 +281,12 @@
                         MathUtils.lerp(
                             translationDistance,
                             0,
-                            Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it)
+                            Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(legacyShadeExpansion)
                         )
                     }
                 }
             }
+            .distinctUntilChanged()
 
     val clockShouldBeCentered: Flow<Boolean> = repository.clockShouldBeCentered
 
@@ -345,10 +346,6 @@
         repository.setQuickSettingsVisible(isVisible)
     }
 
-    fun setClockPosition(x: Int, y: Int) {
-        repository.setClockPosition(x, y)
-    }
-
     fun setAlpha(alpha: Float) {
         repository.setKeyguardAlpha(alpha)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index dc1f33d..fc95ec9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -73,7 +73,6 @@
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
@@ -148,6 +147,7 @@
                             viewModel.alpha(viewState).collect { alpha ->
                                 view.alpha = alpha
                                 childViews[statusViewId]?.alpha = alpha
+                                childViews[burnInLayerId]?.alpha = alpha
                             }
                         }
                     }
@@ -195,66 +195,68 @@
                             // large clock isn't added to burnInLayer due to its scale transition
                             // so we also need to add translation to it here
                             // same as translationX
-                            burnInParams
-                                .flatMapLatest { params -> viewModel.translationY(params) }
-                                .collect { y ->
-                                    childViews[burnInLayerId]?.translationY = y
-                                    childViews[largeClockId]?.translationY = y
-                                    childViews[aodNotificationIconContainerId]?.translationY = y
-                                }
+                            viewModel.translationY.collect { y ->
+                                childViews[burnInLayerId]?.translationY = y
+                                childViews[largeClockId]?.translationY = y
+                                childViews[aodNotificationIconContainerId]?.translationY = y
+                            }
                         }
 
                         launch {
-                            burnInParams
-                                .flatMapLatest { params -> viewModel.translationX(params) }
-                                .collect { state ->
-                                    val px = state.value ?: return@collect
-                                    when {
-                                        state.isToOrFrom(KeyguardState.AOD) -> {
-                                            childViews[largeClockId]?.translationX = px
-                                            childViews[burnInLayerId]?.translationX = px
-                                            childViews[aodNotificationIconContainerId]
-                                                ?.translationX = px
-                                        }
-                                        state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> {
-                                            for ((key, childView) in childViews.entries) {
-                                                when (key) {
-                                                    indicationArea,
-                                                    startButton,
-                                                    endButton,
-                                                    lockIcon -> {
-                                                        // Do not move these views
-                                                    }
-                                                    else -> childView.translationX = px
+                            viewModel.translationX.collect { state ->
+                                val px = state.value ?: return@collect
+                                when {
+                                    state.isToOrFrom(KeyguardState.AOD) -> {
+                                        childViews[largeClockId]?.translationX = px
+                                        childViews[burnInLayerId]?.translationX = px
+                                        childViews[aodNotificationIconContainerId]?.translationX =
+                                            px
+                                    }
+                                    state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> {
+                                        for ((key, childView) in childViews.entries) {
+                                            when (key) {
+                                                indicationArea,
+                                                startButton,
+                                                endButton,
+                                                lockIcon -> {
+                                                    // Do not move these views
                                                 }
+                                                else -> childView.translationX = px
                                             }
                                         }
                                     }
                                 }
+                            }
                         }
 
                         launch {
-                            burnInParams
-                                .flatMapLatest { params -> viewModel.scale(params) }
-                                .collect { scaleViewModel ->
-                                    if (scaleViewModel.scaleClockOnly) {
-                                        // For clocks except weather clock, we have scale transition
-                                        // besides translate
-                                        childViews[largeClockId]?.let {
-                                            it.scaleX = scaleViewModel.scale
-                                            it.scaleY = scaleViewModel.scale
-                                        }
-                                    } else {
-                                        // For weather clock, large clock should have only scale
-                                        // transition with other parts in burnInLayer
-                                        childViews[burnInLayerId]?.scaleX = scaleViewModel.scale
-                                        childViews[burnInLayerId]?.scaleY = scaleViewModel.scale
-                                        childViews[aodNotificationIconContainerId]?.scaleX =
-                                            scaleViewModel.scale
-                                        childViews[aodNotificationIconContainerId]?.scaleY =
-                                            scaleViewModel.scale
+                            viewModel.scale.collect { scaleViewModel ->
+                                if (scaleViewModel.scaleClockOnly) {
+                                    // For clocks except weather clock, we have scale transition
+                                    // besides translate
+                                    childViews[largeClockId]?.let {
+                                        it.scaleX = scaleViewModel.scale
+                                        it.scaleY = scaleViewModel.scale
                                     }
+                                    // Make sure to reset these views, or they will be invisible
+                                    if (childViews[burnInLayerId]?.scaleX != 1f) {
+                                        childViews[burnInLayerId]?.scaleX = 1f
+                                        childViews[burnInLayerId]?.scaleY = 1f
+                                        childViews[aodNotificationIconContainerId]?.scaleX = 1f
+                                        childViews[aodNotificationIconContainerId]?.scaleY = 1f
+                                        view.requestLayout()
+                                    }
+                                } else {
+                                    // For weather clock, large clock should have only scale
+                                    // transition with other parts in burnInLayer
+                                    childViews[burnInLayerId]?.scaleX = scaleViewModel.scale
+                                    childViews[burnInLayerId]?.scaleY = scaleViewModel.scale
+                                    childViews[aodNotificationIconContainerId]?.scaleX =
+                                        scaleViewModel.scale
+                                    childViews[aodNotificationIconContainerId]?.scaleY =
+                                        scaleViewModel.scale
                                 }
+                            }
                         }
 
                         if (NotificationIconContainerRefactor.isEnabled) {
@@ -311,6 +313,8 @@
                         }
                     }
 
+                    launch { burnInParams.collect { viewModel.updateBurnInParams(it) } }
+
                     if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {
                         launch {
                             deviceEntryHapticsInteractor.playSuccessHaptic.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 98bebd0..88ce9dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -21,6 +21,8 @@
 import android.view.View
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
@@ -37,24 +39,24 @@
     private val clockViewModel: KeyguardClockViewModel,
 ) : KeyguardSection() {
     private lateinit var burnInLayer: AodBurnInLayer
+    // The burn-in layer requires at least 1 view at all times
+    private val emptyView: View by lazy {
+        View(context, null).apply {
+            id = R.id.burn_in_layer_empty_view
+            visibility = View.GONE
+        }
+    }
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (!migrateClocksToBlueprint()) {
             return
         }
 
-        // The burn-in layer requires at least 1 view at all times
-        val emptyView = View(context, null).apply { id = View.generateViewId() }
         constraintLayout.addView(emptyView)
         burnInLayer =
             AodBurnInLayer(context).apply {
                 id = R.id.burn_in_layer
                 registerListener(rootView)
                 addView(emptyView)
-                if (!migrateClocksToBlueprint()) {
-                    val statusView =
-                        constraintLayout.requireViewById<View>(R.id.keyguard_status_view)
-                    addView(statusView)
-                }
             }
         constraintLayout.addView(burnInLayer)
     }
@@ -70,6 +72,13 @@
         if (!migrateClocksToBlueprint()) {
             return
         }
+
+        constraintSet.apply {
+            // The empty view should not occupy any space
+            constrainHeight(R.id.burn_in_layer_empty_view, 1)
+            constrainWidth(R.id.burn_in_layer_empty_view, 0)
+            connect(R.id.burn_in_layer_empty_view, BOTTOM, PARENT_ID, BOTTOM)
+        }
     }
 
     override fun removeViews(constraintLayout: ConstraintLayout) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt
index 9edb4d1..bbe5fed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt
@@ -23,13 +23,19 @@
 import com.android.systemui.deviceentry.shared.model.FaceTimeoutMessage
 import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
 import com.android.systemui.deviceentry.shared.model.FingerprintMessage
+import com.android.systemui.statusbar.KeyguardIndicationController.DEFAULT_MESSAGE_TIME
+import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
 import kotlinx.coroutines.flow.filterNot
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
 
 /** View model for the alternate bouncer message area. */
 @ExperimentalCoroutinesApi
@@ -38,19 +44,32 @@
 constructor(
     biometricMessageInteractor: BiometricMessageInteractor,
     alternateBouncerInteractor: AlternateBouncerInteractor,
+    systemClock: com.android.systemui.util.time.SystemClock,
 ) {
-
-    private val faceMessage: Flow<FaceMessage> =
-        biometricMessageInteractor.faceMessage.filterNot { it is FaceTimeoutMessage }
+    private val fingerprintMessageWithTimestamp: Flow<Pair<FingerprintMessage?, Long>> =
+        biometricMessageInteractor.fingerprintMessage
+            .filterNot { it is FingerprintLockoutMessage }
+            .map { Pair(it, systemClock.uptimeMillis()) }
+            .filterIsInstance<Pair<FingerprintMessage?, Long>>()
+            .onStart { emit(Pair(null, -3500L)) }
     private val fingerprintMessage: Flow<FingerprintMessage> =
-        biometricMessageInteractor.fingerprintMessage.filterNot { it is FingerprintLockoutMessage }
+        fingerprintMessageWithTimestamp.filter { it.first != null }.map { it.first!! }
+    private val faceMessage: Flow<FaceMessage> =
+        biometricMessageInteractor.faceMessage
+            .filterNot { it is FaceTimeoutMessage }
+            // Don't show face messages if within the default message time for fp messages to show
+            .sample(fingerprintMessageWithTimestamp, ::Pair)
+            .filter { (_, fpMessage) ->
+                (systemClock.uptimeMillis() - fpMessage.second) >= DEFAULT_MESSAGE_TIME
+            }
+            .map { (faceMsg, _) -> faceMsg }
 
     val message: Flow<BiometricMessage?> =
         alternateBouncerInteractor.isVisible.flatMapLatest { isVisible ->
             if (isVisible) {
                 merge(
-                    faceMessage,
                     fingerprintMessage,
+                    faceMessage,
                 )
             } else {
                 flowOf(null)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index 7be390a..f961e08 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -18,6 +18,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.util.Log
 import android.util.MathUtils
 import com.android.app.animation.Interpolators
 import com.android.keyguard.KeyguardClockSwitch
@@ -62,23 +63,26 @@
     private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
     private val keyguardClockViewModel: KeyguardClockViewModel,
 ) {
-    /** Horizontal translation for elements that need to apply anti-burn-in tactics. */
-    fun translationX(
-        params: BurnInParameters,
-    ): Flow<Float> {
-        return burnIn(params).map { it.translationX.toFloat() }
-    }
+    private val TAG = "AodBurnInViewModel"
 
-    /** Vertical translation for elements that need to apply anti-burn-in tactics. */
-    fun translationY(
-        params: BurnInParameters,
-    ): Flow<Float> {
+    /** All burn-in movement: x,y,scale, to shift items and prevent burn-in */
+    fun movement(
+        burnInParams: BurnInParameters,
+    ): Flow<BurnInModel> {
+        val params =
+            if (burnInParams.minViewY < burnInParams.topInset) {
+                // minViewY should never be below the inset. Correct it if needed
+                Log.w(TAG, "minViewY is below topInset: $burnInParams")
+                burnInParams.copy(minViewY = burnInParams.topInset)
+            } else {
+                burnInParams
+            }
         return configurationInteractor
             .dimensionPixelSize(R.dimen.keyguard_enter_from_top_translation_y)
             .flatMapLatest { enterFromTopAmount ->
                 combine(
                     keyguardInteractor.keyguardTranslationY.onStart { emit(0f) },
-                    burnIn(params).map { it.translationY.toFloat() }.onStart { emit(0f) },
+                    burnIn(params).onStart { emit(BurnInModel()) },
                     goneToAodTransitionViewModel
                         .enterFromTopTranslationY(enterFromTopAmount)
                         .onStart { emit(StateToValue()) },
@@ -88,32 +92,26 @@
                     aodToLockscreenTransitionViewModel.translationY(params.translationY).onStart {
                         emit(StateToValue())
                     },
-                ) { keyguardTranslationY, burnInY, goneToAod, occludedToLockscreen, aodToLockscreen
-                    ->
-                    if (isInTransition(aodToLockscreen.transitionState)) {
-                        aodToLockscreen.value ?: 0f
-                    } else if (isInTransition(goneToAod.transitionState)) {
-                        (goneToAod.value ?: 0f) + burnInY
-                    } else {
-                        burnInY + occludedToLockscreen + keyguardTranslationY
-                    }
+                ) {
+                    keyguardTranslationY,
+                    burnInModel,
+                    goneToAod,
+                    occludedToLockscreen,
+                    aodToLockscreen ->
+                    val translationY =
+                        if (isInTransition(aodToLockscreen.transitionState)) {
+                            aodToLockscreen.value ?: 0f
+                        } else if (isInTransition(goneToAod.transitionState)) {
+                            (goneToAod.value ?: 0f) + burnInModel.translationY
+                        } else {
+                            burnInModel.translationY + occludedToLockscreen + keyguardTranslationY
+                        }
+                    burnInModel.copy(translationY = translationY.toInt())
                 }
             }
             .distinctUntilChanged()
     }
 
-    /** Scale for elements that need to apply anti-burn-in tactics. */
-    fun scale(
-        params: BurnInParameters,
-    ): Flow<BurnInScaleViewModel> {
-        return burnIn(params).map {
-            BurnInScaleViewModel(
-                scale = it.scale,
-                scaleClockOnly = it.scaleClockOnly,
-            )
-        }
-    }
-
     private fun isInTransition(state: TransitionState): Boolean {
         return state == STARTED || state == RUNNING
     }
@@ -125,7 +123,10 @@
             keyguardTransitionInteractor.dozeAmountTransition.map {
                 Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value)
             },
-            burnInInteractor.keyguardBurnIn,
+            burnInInteractor.burnIn(
+                xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
+                yDimenResourceId = R.dimen.burn_in_prevention_offset_y
+            ),
         ) { interpolated, burnIn ->
             val useScaleOnly =
                 (clockController(params.clockControllerProvider)
@@ -149,7 +150,6 @@
                     } else {
                         max(params.topInset, params.minViewY + burnInY) - params.minViewY
                     }
-
                 BurnInModel(
                     translationX = MathUtils.lerp(0, burnIn.translationX, interpolated).toInt(),
                     translationY = translationY,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
index 6458eda..e35e065 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
@@ -17,10 +17,14 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -32,9 +36,10 @@
 @Inject
 constructor(
     private val keyguardInteractor: KeyguardInteractor,
-    bottomAreaInteractor: KeyguardBottomAreaInteractor,
+    private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
     keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel,
     private val burnInHelperWrapper: BurnInHelperWrapper,
+    private val burnInInteractor: BurnInInteractor,
     private val shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
     configurationInteractor: ConfigurationInteractor,
 ) {
@@ -63,24 +68,37 @@
                 }
                 .distinctUntilChanged()
         }
+
+    private val burnIn: Flow<BurnInModel> =
+        burnInInteractor
+            .burnIn(
+                xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
+                yDimenResourceId = R.dimen.default_burn_in_prevention_offset,
+            )
+            .distinctUntilChanged()
+
     /** An observable for the x-offset by which the indication area should be translated. */
     val indicationAreaTranslationX: Flow<Float> =
-        if (keyguardBottomAreaRefactor()) {
-            keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
+        if (migrateClocksToBlueprint() || keyguardBottomAreaRefactor()) {
+            burnIn.map { it.translationX.toFloat() }
         } else {
             bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
         }
 
     /** Returns an observable for the y-offset by which the indication area should be translated. */
     fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
-        return keyguardInteractor.dozeAmount
-            .map { dozeAmount ->
-                dozeAmount *
-                    (burnInHelperWrapper.burnInOffset(
-                        /* amplitude = */ defaultBurnInOffset * 2,
-                        /* xAxis= */ false,
-                    ) - defaultBurnInOffset)
-            }
-            .distinctUntilChanged()
+        return if (migrateClocksToBlueprint()) {
+            burnIn.map { it.translationY.toFloat() }
+        } else {
+            keyguardInteractor.dozeAmount
+                .map { dozeAmount ->
+                    dozeAmount *
+                        (burnInHelperWrapper.burnInOffset(
+                            /* amplitude = */ defaultBurnInOffset * 2,
+                            /* xAxis= */ false,
+                        ) - defaultBurnInOffset)
+                }
+                .distinctUntilChanged()
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index f848717..5ca9215 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -24,9 +24,11 @@
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.BurnInModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -47,8 +49,11 @@
 import com.android.systemui.util.ui.zip
 import javax.inject.Inject
 import kotlin.math.max
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -57,12 +62,14 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class KeyguardRootViewModel
 @Inject
 constructor(
+    @Application private val scope: CoroutineScope,
     private val deviceEntryInteractor: DeviceEntryInteractor,
     private val dozeParameters: DozeParameters,
     private val keyguardInteractor: KeyguardInteractor,
@@ -102,6 +109,8 @@
     private val aodAlphaViewModel: AodAlphaViewModel,
     private val shadeInteractor: ShadeInteractor,
 ) {
+    private var burnInJob: Job? = null
+    private val burnInModel = MutableStateFlow(BurnInModel())
 
     val burnInLayerVisibility: Flow<Int> =
         keyguardTransitionInteractor.startedKeyguardState
@@ -213,21 +222,33 @@
     /** For elements that appear and move during the animation -> AOD */
     val burnInLayerAlpha: Flow<Float> = aodAlphaViewModel.alpha
 
-    fun translationY(params: BurnInParameters): Flow<Float> {
-        return aodBurnInViewModel.translationY(params)
-    }
+    val translationY: Flow<Float> = burnInModel.map { it.translationY.toFloat() }
 
-    fun translationX(params: BurnInParameters): Flow<StateToValue> {
-        return merge(
-            aodBurnInViewModel.translationX(params).map { StateToValue(to = AOD, value = it) },
+    val translationX: Flow<StateToValue> =
+        merge(
+            burnInModel.map { StateToValue(to = AOD, value = it.translationX.toFloat()) },
             lockscreenToGlanceableHubTransitionViewModel.keyguardTranslationX,
             glanceableHubToLockscreenTransitionViewModel.keyguardTranslationX,
         )
+
+    fun updateBurnInParams(params: BurnInParameters) {
+        burnInJob?.cancel()
+
+        burnInJob =
+            scope.launch {
+                aodBurnInViewModel.movement(params).collect {
+                    burnInModel.value = it
+                }
+            }
     }
 
-    fun scale(params: BurnInParameters): Flow<BurnInScaleViewModel> {
-        return aodBurnInViewModel.scale(params)
-    }
+    val scale: Flow<BurnInScaleViewModel> =
+        burnInModel.map {
+            BurnInScaleViewModel(
+                scale = it.scale,
+                scaleClockOnly = it.scaleClockOnly,
+            )
+        }
 
     /** Is the notification icon container visible? */
     val isNotifIconContainerVisible: Flow<AnimatedValue<Boolean>> =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index 20f3c4d..bd66843 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -103,14 +103,27 @@
     public override fun handleClick(view: View?) {
         if (isRecording) {
             isRecording = false
-            stopScreenRecord()
+            stopIssueRecordingService()
         } else {
             mUiHandler.post { showPrompt(view) }
         }
         refreshState()
     }
 
-    private fun stopScreenRecord() =
+    private fun startIssueRecordingService(screenRecord: Boolean, winscopeTracing: Boolean) =
+        PendingIntent.getForegroundService(
+                userContextProvider.userContext,
+                RecordingService.REQUEST_CODE,
+                IssueRecordingService.getStartIntent(
+                    userContextProvider.userContext,
+                    screenRecord,
+                    winscopeTracing
+                ),
+                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+            )
+            .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
+
+    private fun stopIssueRecordingService() =
         PendingIntent.getService(
                 userContextProvider.userContext,
                 RecordingService.REQUEST_CODE,
@@ -124,6 +137,7 @@
             delegateFactory
                 .create {
                     isRecording = true
+                    startIssueRecordingService(it.screenRecord, it.winscopeTracing)
                     refreshState()
                 }
                 .createDialog()
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingConfig.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingConfig.kt
new file mode 100644
index 0000000..bb3b654
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingConfig.kt
@@ -0,0 +1,19 @@
+/*
+ * 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.recordissue
+
+data class IssueRecordingConfig(val screenRecord: Boolean, val winscopeTracing: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index f487258..55d7f8e 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -20,7 +20,9 @@
 import android.content.Context
 import android.content.Intent
 import android.content.res.Resources
+import android.net.Uri
 import android.os.Handler
+import android.os.UserHandle
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.dagger.qualifiers.LongRunning
 import com.android.systemui.dagger.qualifiers.Main
@@ -30,6 +32,8 @@
 import com.android.systemui.screenrecord.RecordingServiceStrings
 import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
+import com.android.traceur.FileSender
+import com.android.traceur.TraceUtils
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
@@ -60,9 +64,89 @@
 
     override fun provideRecordingServiceStrings(): RecordingServiceStrings = IrsStrings(resources)
 
+    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+        when (intent?.action) {
+            ACTION_START -> {
+                TraceUtils.traceStart(
+                    contentResolver,
+                    DEFAULT_TRACE_TAGS,
+                    DEFAULT_BUFFER_SIZE,
+                    DEFAULT_IS_INCLUDING_WINSCOPE,
+                    DEFAULT_IS_INCLUDING_APP_TRACE,
+                    DEFAULT_IS_LONG_TRACE,
+                    DEFAULT_ATTACH_TO_BUGREPORT,
+                    DEFAULT_MAX_TRACE_SIZE,
+                    DEFAULT_MAX_TRACE_DURATION_IN_MINUTES
+                )
+                if (!intent.getBooleanExtra(EXTRA_SCREEN_RECORD, false)) {
+                    // If we don't want to record the screen, the ACTION_SHOW_START_NOTIF action
+                    // will circumvent the RecordingService's screen recording start code.
+                    return super.onStartCommand(Intent(ACTION_SHOW_START_NOTIF), flags, startId)
+                }
+            }
+            ACTION_STOP,
+            ACTION_STOP_NOTIF -> {
+                TraceUtils.traceStop(contentResolver)
+            }
+            ACTION_SHARE -> {
+                shareRecording(intent)
+
+                // Unlike all other actions, action_share has different behavior for the screen
+                // recording qs tile than it does for the record issue qs tile. Return sticky to
+                // avoid running any of the base class' code for this action.
+                return START_STICKY
+            }
+            else -> {}
+        }
+        return super.onStartCommand(intent, flags, startId)
+    }
+
+    private fun shareRecording(intent: Intent) {
+        val files = TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).get()
+        val traceUris: MutableList<Uri> = FileSender.getUriForFiles(this, files, AUTHORITY)
+
+        if (
+            intent.hasExtra(EXTRA_PATH) && intent.getStringExtra(EXTRA_PATH)?.isNotEmpty() == true
+        ) {
+            traceUris.add(Uri.parse(intent.getStringExtra(EXTRA_PATH)))
+        }
+
+        val sendIntent =
+            FileSender.buildSendIntent(this, traceUris).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+        if (mNotificationId != NOTIF_BASE_ID) {
+            val currentUserId = mUserContextTracker.userContext.userId
+            mNotificationManager.cancelAsUser(null, mNotificationId, UserHandle(currentUserId))
+        }
+
+        // TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity
+        mKeyguardDismissUtil.executeWhenUnlocked(
+            {
+                startActivity(sendIntent)
+                false
+            },
+            false,
+            false
+        )
+    }
+
     companion object {
         private const val TAG = "IssueRecordingService"
         private const val CHANNEL_ID = "issue_record"
+        private const val EXTRA_SCREEN_RECORD = "extra_screenRecord"
+        private const val EXTRA_WINSCOPE_TRACING = "extra_winscopeTracing"
+
+        private val DEFAULT_TRACE_TAGS = listOf<String>()
+        private const val DEFAULT_BUFFER_SIZE = 16384
+        private const val DEFAULT_IS_INCLUDING_WINSCOPE = true
+        private const val DEFAULT_IS_LONG_TRACE = false
+        private const val DEFAULT_IS_INCLUDING_APP_TRACE = true
+        private const val DEFAULT_ATTACH_TO_BUGREPORT = true
+        private const val DEFAULT_MAX_TRACE_SIZE = 10240
+        private const val DEFAULT_MAX_TRACE_DURATION_IN_MINUTES = 30
+
+        private val TRACE_FILE_NAME = TraceUtils.getOutputFilename(TraceUtils.RecordingType.TRACE)
+        private const val AUTHORITY = "com.android.systemui.fileprovider"
 
         /**
          * Get an intent to stop the issue recording service.
@@ -71,7 +155,7 @@
          * @return
          */
         fun getStopIntent(context: Context): Intent =
-            Intent(context, RecordingService::class.java)
+            Intent(context, IssueRecordingService::class.java)
                 .setAction(ACTION_STOP)
                 .putExtra(Intent.EXTRA_USER_HANDLE, context.userId)
 
@@ -80,8 +164,15 @@
          *
          * @param context Context from the requesting activity
          */
-        fun getStartIntent(context: Context): Intent =
-            Intent(context, RecordingService::class.java).setAction(ACTION_START)
+        fun getStartIntent(
+            context: Context,
+            screenRecord: Boolean,
+            winscopeTracing: Boolean
+        ): Intent =
+            Intent(context, IssueRecordingService::class.java)
+                .setAction(ACTION_START)
+                .putExtra(EXTRA_SCREEN_RECORD, screenRecord)
+                .putExtra(EXTRA_WINSCOPE_TRACING, winscopeTracing)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index 1c07d00..ff18a11 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -17,8 +17,6 @@
 package com.android.systemui.recordissue
 
 import android.annotation.SuppressLint
-import android.app.BroadcastOptions
-import android.app.PendingIntent
 import android.content.Context
 import android.content.res.ColorStateList
 import android.graphics.Color
@@ -43,8 +41,6 @@
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate
 import com.android.systemui.qs.tiles.RecordIssueTile
 import com.android.systemui.res.R
-import com.android.systemui.screenrecord.RecordingService
-import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -52,12 +48,12 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import java.util.concurrent.Executor
+import java.util.function.Consumer
 
 class RecordIssueDialogDelegate
 @AssistedInject
 constructor(
     private val factory: SystemUIDialog.Factory,
-    private val userContextProvider: UserContextProvider,
     private val userTracker: UserTracker,
     private val flags: FeatureFlagsClassic,
     @Background private val bgExecutor: Executor,
@@ -66,14 +62,14 @@
     private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
     private val userFileManager: UserFileManager,
     private val screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate,
-    @Assisted private val onStarted: Runnable,
+    @Assisted private val onStarted: Consumer<IssueRecordingConfig>,
 ) : SystemUIDialog.Delegate {
 
     /** To inject dependencies and allow for easier testing */
     @AssistedFactory
     interface Factory {
         /** Create a dialog object */
-        fun create(onStarted: Runnable): RecordIssueDialogDelegate
+        fun create(onStarted: Consumer<IssueRecordingConfig>): RecordIssueDialogDelegate
     }
 
     @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
@@ -87,10 +83,12 @@
             setIcon(R.drawable.qs_record_issue_icon_off)
             setNegativeButton(R.string.cancel) { _, _ -> dismiss() }
             setPositiveButton(R.string.qs_record_issue_start) { _, _ ->
-                onStarted.run()
-                if (screenRecordSwitch.isChecked) {
-                    requestScreenCapture()
-                }
+                onStarted.accept(
+                    IssueRecordingConfig(
+                        screenRecordSwitch.isChecked,
+                        true /* TODO: Base this on issueType selected */
+                    )
+                )
                 dismiss()
             }
         }
@@ -177,13 +175,4 @@
             show()
         }
     }
-
-    private fun requestScreenCapture() =
-        PendingIntent.getForegroundService(
-                userContextProvider.userContext,
-                RecordingService.REQUEST_CODE,
-                IssueRecordingService.getStartIntent(userContextProvider.userContext),
-                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
-            )
-            .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index b355d2d..ac94f39 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -60,25 +60,27 @@
     public static final int REQUEST_CODE = 2;
 
     private static final int USER_ID_NOT_SPECIFIED = -1;
-    private static final int NOTIF_BASE_ID = 4273;
+    protected static final int NOTIF_BASE_ID = 4273;
     private static final String TAG = "RecordingService";
     private static final String CHANNEL_ID = "screen_record";
     private static final String GROUP_KEY = "screen_record_saved";
     private static final String EXTRA_RESULT_CODE = "extra_resultCode";
-    private static final String EXTRA_PATH = "extra_path";
+    protected static final String EXTRA_PATH = "extra_path";
     private static final String EXTRA_AUDIO_SOURCE = "extra_useAudio";
     private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
     private static final String EXTRA_CAPTURE_TARGET = "extra_captureTarget";
 
     protected static final String ACTION_START = "com.android.systemui.screenrecord.START";
+    protected static final String ACTION_SHOW_START_NOTIF =
+            "com.android.systemui.screenrecord.START_NOTIF";
     protected static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP";
-    private static final String ACTION_STOP_NOTIF =
+    protected static final String ACTION_STOP_NOTIF =
             "com.android.systemui.screenrecord.STOP_FROM_NOTIF";
-    private static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
+    protected static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
     private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
 
     private final RecordingController mController;
-    private final KeyguardDismissUtil mKeyguardDismissUtil;
+    protected final KeyguardDismissUtil mKeyguardDismissUtil;
     private final Handler mMainHandler;
     private ScreenRecordingAudioSource mAudioSource;
     private boolean mShowTaps;
@@ -86,9 +88,9 @@
     private ScreenMediaRecorder mRecorder;
     private final Executor mLongExecutor;
     private final UiEventLogger mUiEventLogger;
-    private final NotificationManager mNotificationManager;
-    private final UserContextProvider mUserContextTracker;
-    private int mNotificationId = NOTIF_BASE_ID;
+    protected final NotificationManager mNotificationManager;
+    protected final UserContextProvider mUserContextTracker;
+    protected int mNotificationId = NOTIF_BASE_ID;
     private RecordingServiceStrings mStrings;
 
     @Inject
@@ -185,7 +187,10 @@
                     return Service.START_NOT_STICKY;
                 }
                 break;
-
+            case ACTION_SHOW_START_NOTIF:
+                createRecordingNotification();
+                mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_START);
+                break;
             case ACTION_STOP_NOTIF:
             case ACTION_STOP:
                 // only difference for actions is the log event
@@ -232,6 +237,7 @@
         super.onCreate();
     }
 
+    @Nullable
     @VisibleForTesting
     protected ScreenMediaRecorder getRecorder() {
         return mRecorder;
@@ -337,8 +343,9 @@
     }
 
     @VisibleForTesting
-    protected Notification createSaveNotification(ScreenMediaRecorder.SavedRecording recording) {
-        Uri uri = recording.getUri();
+    protected Notification createSaveNotification(
+            @Nullable ScreenMediaRecorder.SavedRecording recording) {
+        Uri uri = recording != null ? recording.getUri() : null;
         Intent viewIntent = new Intent(Intent.ACTION_VIEW)
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION)
                 .setDataAndType(uri, "video/mp4");
@@ -349,7 +356,7 @@
                 PendingIntent.getService(
                         this,
                         REQUEST_CODE,
-                        getShareIntent(this, uri.toString()),
+                        getShareIntent(this, uri != null ? uri.toString() : null),
                         PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
                 .build();
 
@@ -371,9 +378,10 @@
                 .addExtras(extras);
 
         // Add thumbnail if available
-        if (recording.getThumbnail() != null) {
+        Icon thumbnail = recording != null ? recording.getThumbnail() : null;
+        if (thumbnail != null) {
             Notification.BigPictureStyle pictureStyle = new Notification.BigPictureStyle()
-                    .bigPicture(recording.getThumbnail())
+                    .bigPicture(thumbnail)
                     .showBigPictureWhenCollapsed(true);
             builder.setStyle(pictureStyle);
         }
@@ -408,27 +416,29 @@
         }
         Log.d(getTag(), "notifying for user " + userId);
         setTapsVisible(mOriginalShowTaps);
-        if (getRecorder() != null) {
-            try {
+        try {
+            if (getRecorder() != null) {
                 getRecorder().end();
-                saveRecording(userId);
-            } catch (RuntimeException exception) {
+            }
+            saveRecording(userId);
+        } catch (RuntimeException exception) {
+            if (getRecorder() != null) {
                 // RuntimeException could happen if the recording stopped immediately after starting
                 // let's release the recorder and delete all temporary files in this case
                 getRecorder().release();
-                showErrorToast(R.string.screenrecord_start_error);
-                Log.e(getTag(), "stopRecording called, but there was an error when ending"
-                        + "recording");
-                exception.printStackTrace();
-                createErrorNotification();
-            } catch (Throwable throwable) {
+            }
+            showErrorToast(R.string.screenrecord_start_error);
+            Log.e(getTag(), "stopRecording called, but there was an error when ending"
+                    + "recording");
+            exception.printStackTrace();
+            createErrorNotification();
+        } catch (Throwable throwable) {
+            if (getRecorder() != null) {
                 // Something unexpected happen, SystemUI will crash but let's delete
                 // the temporary files anyway
                 getRecorder().release();
-                throw new RuntimeException(throwable);
             }
-        } else {
-            Log.e(getTag(), "stopRecording called, but recorder was null");
+            throw new RuntimeException(throwable);
         }
         updateState(false);
         stopForeground(STOP_FOREGROUND_DETACH);
@@ -443,7 +453,8 @@
         mLongExecutor.execute(() -> {
             try {
                 Log.d(getTag(), "saving recording");
-                Notification notification = createSaveNotification(getRecorder().save());
+                Notification notification = createSaveNotification(
+                        getRecorder() != null ? getRecorder().save() : null);
                 postGroupNotification(currentUser);
                 mNotificationManager.notifyAsUser(null, mNotificationId,  notification,
                         currentUser);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
index 2294fc0..d8c3850 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -22,12 +22,14 @@
 import android.graphics.Bitmap
 import android.graphics.Rect
 import android.graphics.drawable.Drawable
+import android.util.Log
 import android.view.Display
 import android.view.LayoutInflater
 import android.view.ScrollCaptureResponse
 import android.view.View
 import android.view.ViewTreeObserver
 import android.view.WindowInsets
+import android.window.OnBackInvokedCallback
 import android.window.OnBackInvokedDispatcher
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.flags.FeatureFlags
@@ -40,7 +42,6 @@
 class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy {
     override val view: ScreenshotView =
         LayoutInflater.from(context).inflate(R.layout.screenshot, null) as ScreenshotView
-    override val internalInsetsListener: ViewTreeObserver.OnComputeInternalInsetsListener
     override val screenshotPreview: View
 
     override var defaultDisplay: Int = Display.DEFAULT_DISPLAY
@@ -51,6 +52,9 @@
         set(value) {
             view.setDefaultTimeoutMillis(value)
         }
+    override var onBackInvokedCallback: OnBackInvokedCallback = OnBackInvokedCallback {
+        Log.wtf(TAG, "OnBackInvoked called before being set!")
+    }
     override var onKeyListener: View.OnKeyListener? = null
         set(value) {
             view.setOnKeyListener(value)
@@ -84,7 +88,35 @@
         get() = view.isPendingSharedTransition
 
     init {
-        internalInsetsListener = view
+
+        view.addOnAttachStateChangeListener(
+            object : View.OnAttachStateChangeListener {
+                override fun onViewAttachedToWindow(view: View) {
+                    if (LogConfig.DEBUG_INPUT) {
+                        Log.d(TAG, "Registering Predictive Back callback")
+                    }
+                    view
+                        .findOnBackInvokedDispatcher()
+                        ?.registerOnBackInvokedCallback(
+                            OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+                            onBackInvokedCallback
+                        )
+                }
+
+                override fun onViewDetachedFromWindow(view: View) {
+                    if (LogConfig.DEBUG_INPUT) {
+                        Log.d(TAG, "Unregistering Predictive Back callback")
+                    }
+                    view
+                        .findOnBackInvokedDispatcher()
+                        ?.unregisterOnBackInvokedCallback(onBackInvokedCallback)
+                }
+            }
+        )
+        if (LogConfig.DEBUG_WINDOW) {
+            Log.d(TAG, "adding OnComputeInternalInsetsListener")
+        }
+        view.viewTreeObserver.addOnComputeInternalInsetsListener(view)
         screenshotPreview = view.screenshotPreview
     }
 
@@ -139,12 +171,6 @@
 
     override fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
 
-    override fun addOnAttachStateChangeListener(listener: View.OnAttachStateChangeListener) =
-        view.addOnAttachStateChangeListener(listener)
-
-    override fun findOnBackInvokedDispatcher(): OnBackInvokedDispatcher? =
-        view.findOnBackInvokedDispatcher()
-
     override fun getViewTreeObserver(): ViewTreeObserver = view.viewTreeObserver
 
     override fun post(runnable: Runnable) {
@@ -156,4 +182,8 @@
             return LegacyScreenshotViewProxy(context)
         }
     }
+
+    companion object {
+        private const val TAG = "LegacyScreenshotViewProxy"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 13448d2..1ca9b98 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -76,8 +76,6 @@
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
-import android.window.OnBackInvokedCallback;
-import android.window.OnBackInvokedDispatcher;
 import android.window.WindowContext;
 
 import com.android.internal.app.ChooserActivity;
@@ -265,13 +263,6 @@
     private final UserManager mUserManager;
     private final AssistContentRequester mAssistContentRequester;
 
-    private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
-        if (DEBUG_INPUT) {
-            Log.d(TAG, "Predictive Back callback dispatched");
-        }
-        respondToKeyDismissal();
-    };
-
     private final MessageContainerController mMessageContainerController;
     private Bitmap mScreenBitmap;
     private SaveImageInBackgroundTask mSaveInBgTask;
@@ -594,27 +585,13 @@
         }
 
         mMessageContainerController.setView(mViewProxy.getView());
-        mViewProxy.addOnAttachStateChangeListener(
-                new View.OnAttachStateChangeListener() {
-                    @Override
-                    public void onViewAttachedToWindow(@NonNull View v) {
-                        if (DEBUG_INPUT) {
-                            Log.d(TAG, "Registering Predictive Back callback");
-                        }
-                        mViewProxy.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(
-                                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback);
-                    }
-
-                    @Override
-                    public void onViewDetachedFromWindow(@NonNull View v) {
-                        if (DEBUG_INPUT) {
-                            Log.d(TAG, "Unregistering Predictive Back callback");
-                        }
-                        mViewProxy.findOnBackInvokedDispatcher()
-                                .unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
-                    }
-                });
         mViewProxy.setLogger(mUiEventLogger);
+        mViewProxy.setOnBackInvokedCallback(() -> {
+            if (DEBUG_INPUT) {
+                Log.d(TAG, "Predictive Back callback dispatched");
+            }
+            respondToKeyDismissal();
+        });
         mViewProxy.setCallbacks(new ScreenshotView.ScreenshotViewCallback() {
             @Override
             public void onUserInteraction() {
@@ -657,11 +634,6 @@
         });
 
         if (DEBUG_WINDOW) {
-            Log.d(TAG, "adding OnComputeInternalInsetsListener");
-        }
-        mViewProxy.getViewTreeObserver().addOnComputeInternalInsetsListener(
-                mViewProxy.getInternalInsetsListener());
-        if (DEBUG_WINDOW) {
             Log.d(TAG, "setContentView: " + mViewProxy.getView());
         }
         setContentView(mViewProxy.getView());
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
index 0064521..381404a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -28,18 +28,18 @@
 import android.view.ViewGroup
 import android.view.ViewTreeObserver
 import android.view.WindowInsets
-import android.window.OnBackInvokedDispatcher
+import android.window.OnBackInvokedCallback
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.flags.FeatureFlags
 
 /** Abstraction of the surface between ScreenshotController and ScreenshotView */
 interface ScreenshotViewProxy {
     val view: ViewGroup
-    val internalInsetsListener: ViewTreeObserver.OnComputeInternalInsetsListener
     val screenshotPreview: View
 
     var defaultDisplay: Int
     var defaultTimeoutMillis: Long
+    var onBackInvokedCallback: OnBackInvokedCallback
     var onKeyListener: OnKeyListener?
     var flags: FeatureFlags?
     var packageName: String
@@ -78,8 +78,6 @@
     fun stopInputListening()
     fun requestFocus()
     fun announceForAccessibility(string: String)
-    fun addOnAttachStateChangeListener(listener: View.OnAttachStateChangeListener)
-    fun findOnBackInvokedDispatcher(): OnBackInvokedDispatcher?
     fun getViewTreeObserver(): ViewTreeObserver
     fun post(runnable: Runnable)
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 2fd438b..a1644b2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1288,10 +1288,9 @@
                             mView.getContext().getDisplay());
             mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
             mKeyguardStatusViewController.init();
-        }
 
-        mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
-        mKeyguardStatusViewController.getView().addOnLayoutChangeListener(
+            mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
+            mKeyguardStatusViewController.getView().addOnLayoutChangeListener(
                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
                     int oldHeight = oldBottom - oldTop;
                     if (v.getHeight() != oldHeight) {
@@ -1299,7 +1298,8 @@
                     }
                 });
 
-        updateClockAppearance();
+            updateClockAppearance();
+        }
     }
 
     @Override
@@ -1326,7 +1326,9 @@
 
     private void onSplitShadeEnabledChanged() {
         mShadeLog.logSplitShadeChanged(mSplitShadeEnabled);
-        mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
+        if (!migrateClocksToBlueprint()) {
+            mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
+        }
         // Reset any left over overscroll state. It is a rare corner case but can happen.
         mQsController.setOverScrollAmount(0);
         mScrimController.setNotificationsOverScrollAmount(0);
@@ -1441,11 +1443,13 @@
         mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
                 mStatusBarStateController.getInterpolatedDozeAmount());
 
-        mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
-                mBarState,
-                false,
-                false,
-                mBarState);
+        if (!migrateClocksToBlueprint()) {
+            mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+                    mBarState,
+                    false,
+                    false,
+                    mBarState);
+        }
         if (mKeyguardQsUserSwitchController != null) {
             mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
                     mBarState,
@@ -1665,13 +1669,11 @@
             mKeyguardStatusViewController.setLockscreenClockY(
                     mClockPositionAlgorithm.getExpandedPreferredClockY());
         }
-        if (keyguardBottomAreaRefactor()) {
-            mKeyguardInteractor.setClockPosition(
-                mClockPositionResult.clockX, mClockPositionResult.clockY);
-        } else {
+        if (!(migrateClocksToBlueprint() || keyguardBottomAreaRefactor())) {
             mKeyguardBottomAreaInteractor.setClockPosition(
                 mClockPositionResult.clockX, mClockPositionResult.clockY);
         }
+
         boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
         boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
 
@@ -1749,13 +1751,11 @@
     }
 
     private void updateKeyguardStatusViewAlignment(boolean animate) {
-        boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
-        ConstraintLayout layout;
         if (migrateClocksToBlueprint()) {
-            layout = mKeyguardViewConfigurator.getKeyguardRootView();
-        } else {
-            layout = mNotificationContainerParent;
+            return;
         }
+        boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
+        ConstraintLayout layout = mNotificationContainerParent;
         mKeyguardStatusViewController.updateAlignment(
                 layout, mSplitShadeEnabled, shouldBeCentered, animate);
         mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered));
@@ -3316,6 +3316,9 @@
         /** Updates the views to the initial state for the fold to AOD animation. */
         @Override
         public void prepareFoldToAodAnimation() {
+            if (migrateClocksToBlueprint()) {
+                return;
+            }
             // Force show AOD UI even if we are not locked
             showAodUi();
 
@@ -3337,6 +3340,9 @@
         @Override
         public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
                 Runnable cancelAction) {
+            if (migrateClocksToBlueprint()) {
+                return;
+            }
             final ViewPropertyAnimator viewAnimator = mView.animate();
             viewAnimator.cancel();
             viewAnimator
@@ -3372,6 +3378,9 @@
         /** Cancels fold to AOD transition and resets view state. */
         @Override
         public void cancelFoldToAodAnimation() {
+            if (migrateClocksToBlueprint()) {
+                return;
+            }
             cancelAnimation();
             resetAlpha();
             resetTranslation();
@@ -4446,11 +4455,13 @@
                 }
             }
 
-            mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
-                    statusBarState,
-                    keyguardFadingAway,
-                    goingToFullShade,
-                    mBarState);
+            if (!migrateClocksToBlueprint()) {
+                mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+                        statusBarState,
+                        keyguardFadingAway,
+                        goingToFullShade,
+                        mBarState);
+            }
 
             if (!keyguardBottomAreaRefactor()) {
                 setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 1ec86ae..c904621 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -147,8 +147,9 @@
     private static final int MSG_SHOW_ACTION_TO_UNLOCK = 1;
     private static final int MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON = 2;
     private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300;
+    public static final long DEFAULT_MESSAGE_TIME = 3500;
     public static final long DEFAULT_HIDE_DELAY_MS =
-            3500 + KeyguardIndicationTextView.Y_IN_DURATION;
+            DEFAULT_MESSAGE_TIME + KeyguardIndicationTextView.Y_IN_DURATION;
 
     private final Context mContext;
     private final BroadcastDispatcher mBroadcastDispatcher;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 1a06eec..0091bc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -420,7 +420,7 @@
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
-        if (allowPrivateProfile()){
+        if (privateSpaceFlagsEnabled()) {
             filter.addAction(Intent.ACTION_PROFILE_AVAILABLE);
             filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE);
         }
@@ -813,13 +813,17 @@
     }
 
     private boolean profileAvailabilityActions(String action){
-        return allowPrivateProfile()?
+        return privateSpaceFlagsEnabled()?
                 Objects.equals(action,Intent.ACTION_PROFILE_AVAILABLE)||
                         Objects.equals(action,Intent.ACTION_PROFILE_UNAVAILABLE):
                 Objects.equals(action,Intent.ACTION_MANAGED_PROFILE_AVAILABLE)||
                         Objects.equals(action,Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
     }
 
+    private static boolean privateSpaceFlagsEnabled() {
+        return allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures();
+    }
+
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println("NotificationLockscreenUserManager state:");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 78fc147..3a9cdd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -474,7 +474,10 @@
      */
     fun translationY(params: BurnInParameters): Flow<Float> {
         return combine(
-                aodBurnInViewModel.translationY(params).onStart { emit(0f) },
+                aodBurnInViewModel
+                    .movement(params)
+                    .map { it.translationY.toFloat() }
+                    .onStart { emit(0f) },
                 isOnLockscreenWithoutShade,
                 merge(
                     keyguardInteractor.keyguardTranslationY,
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 44c684c..b5efc44 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -379,7 +379,9 @@
                             + " was received. Deferring... Managed profile? " + isManagedProfile);
                     return;
                 }
-                if (android.os.Flags.allowPrivateProfile() && isPrivateProfile(newUserHandle)) {
+                if (android.os.Flags.allowPrivateProfile()
+                        && android.multiuser.Flags.enablePrivateSpaceFeatures()
+                        && isPrivateProfile(newUserHandle)) {
                     mDeferredThemeEvaluation = true;
                     Log.i(TAG, "Deferring theme for private profile till user setup is complete");
                     return;
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt b/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt
index db300eb..2157fafa 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt
@@ -18,6 +18,7 @@
 
 import android.os.PowerManager
 import android.util.Log
+import com.android.systemui.util.wakelock.WakeLock.Builder.NO_TIMEOUT
 import java.util.concurrent.ConcurrentHashMap
 import java.util.concurrent.atomic.AtomicInteger
 
@@ -36,7 +37,11 @@
     override fun acquire(why: String) {
         val count = activeClients.computeIfAbsent(why) { _ -> AtomicInteger(0) }.incrementAndGet()
         logger?.logAcquire(pmWakeLock, why, count)
-        pmWakeLock.acquire(maxTimeout)
+        if (maxTimeout == NO_TIMEOUT) {
+            pmWakeLock.acquire()
+        } else {
+            pmWakeLock.acquire(maxTimeout)
+        }
     }
 
     override fun release(why: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
index 707751a..f763ee4 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
@@ -130,7 +130,11 @@
                 if (logger != null) {
                     logger.logAcquire(inner, why, count);
                 }
-                inner.acquire(maxTimeout);
+                if (maxTimeout == Builder.NO_TIMEOUT) {
+                    inner.acquire();
+                } else {
+                    inner.acquire(maxTimeout);
+                }
             }
 
             /** @see PowerManager.WakeLock#release() */
@@ -169,6 +173,7 @@
      * An injectable Builder that wraps {@link #createPartial(Context, String, long)}.
      */
     class Builder {
+        public static final long NO_TIMEOUT = -1;
         private final Context mContext;
         private final WakeLockLogger mLogger;
         private String mTag;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
index 593b90a..4ba7cbb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
@@ -21,12 +21,10 @@
 import com.android.settingslib.media.data.repository.SpatializerRepository
 import com.android.settingslib.media.data.repository.SpatializerRepositoryImpl
 import com.android.settingslib.media.domain.interactor.SpatializerInteractor
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import dagger.Module
 import dagger.Provides
 import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.CoroutineScope
 
 /** Spatializer module. */
 @Module
@@ -42,9 +40,8 @@
         @Provides
         fun provdieSpatializerRepository(
             spatializer: Spatializer,
-            @Application scope: CoroutineScope,
             @Background backgroundContext: CoroutineContext,
-        ): SpatializerRepository = SpatializerRepositoryImpl(spatializer, scope, backgroundContext)
+        ): SpatializerRepository = SpatializerRepositoryImpl(spatializer, backgroundContext)
 
         @Provides
         fun provideSpatializerInetractor(repository: SpatializerRepository): SpatializerInteractor =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt
index 8ab563a..6c47aec 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt
@@ -23,3 +23,6 @@
     val icon: Icon,
     val label: CharSequence,
 )
+
+fun ToggleButtonViewModel.toButtonViewModel(): ButtonViewModel =
+    ButtonViewModel(icon = icon, label = label)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
index 9d801fc..9ef07fa 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
@@ -24,5 +24,6 @@
     const val BOTTOM_BAR: VolumePanelComponentKey = "bottom_bar"
     const val VOLUME_SLIDERS: VolumePanelComponentKey = "volume_sliders"
     const val CAPTIONING: VolumePanelComponentKey = "captioning"
+    const val SPATIAL_AUDIO: VolumePanelComponentKey = "spatial_audio"
     const val ANC: VolumePanelComponentKey = "anc"
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
index 4358611..6032bfe 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
@@ -71,10 +71,10 @@
         combine(
                 currentAudioDeviceAttributes,
                 changes.onStart { emit(Unit) },
-                spatializerInteractor.isHeadTrackingAvailable,
-            ) { attributes, _, isHeadTrackingAvailable ->
+            ) { attributes, _,
+                ->
                 attributes ?: return@combine SpatialAudioAvailabilityModel.Unavailable
-                if (isHeadTrackingAvailable) {
+                if (spatializerInteractor.isHeadTrackingAvailable(attributes)) {
                     return@combine SpatialAudioAvailabilityModel.HeadTracking
                 }
                 if (spatializerInteractor.isSpatialAudioAvailable(attributes)) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
index 4e65f60..9735e5c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
@@ -19,6 +19,16 @@
 /** Models spatial audio and head tracking enabled/disabled state. */
 interface SpatialAudioEnabledModel {
 
+    companion object {
+        /** All possible SpatialAudioEnabledModel implementations. */
+        val values =
+            listOf(
+                Disabled,
+                SpatialAudioEnabled,
+                HeadTrackingEnabled,
+            )
+    }
+
     /** Spatial audio is disabled. */
     data object Disabled : SpatialAudioEnabledModel
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
new file mode 100644
index 0000000..9f9275b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.spatial.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Color
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
+
+data class SpatialAudioButtonViewModel(
+    val model: SpatialAudioEnabledModel,
+    val button: ToggleButtonViewModel,
+    val iconColor: Color,
+    val labelColor: Color,
+)
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
new file mode 100644
index 0000000..30715d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.spatial.ui.viewmodel
+
+import android.content.Context
+import com.android.systemui.common.shared.model.Color
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.toButtonViewModel
+import com.android.systemui.volume.panel.component.spatial.domain.SpatialAudioAvailabilityCriteria
+import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+@VolumePanelScope
+class SpatialAudioViewModel
+@Inject
+constructor(
+    @Application private val context: Context,
+    @VolumePanelScope private val scope: CoroutineScope,
+    availabilityCriteria: SpatialAudioAvailabilityCriteria,
+    private val interactor: SpatialAudioComponentInteractor,
+) {
+
+    val spatialAudioButton: StateFlow<ButtonViewModel?> =
+        interactor.isEnabled
+            .map { it.toViewModel(true).toButtonViewModel() }
+            .stateIn(scope, SharingStarted.Eagerly, null)
+
+    val isAvailable: StateFlow<Boolean> =
+        availabilityCriteria.isAvailable().stateIn(scope, SharingStarted.Eagerly, true)
+
+    val spatialAudioButtonByEnabled: StateFlow<List<SpatialAudioButtonViewModel>> =
+        combine(interactor.isEnabled, interactor.isAvailable) { currentIsEnabled, isAvailable ->
+                SpatialAudioEnabledModel.values
+                    .filter {
+                        if (it is SpatialAudioEnabledModel.HeadTrackingEnabled) {
+                            // Spatial audio control can be visible when there is spatial audio
+                            // setting available but not the head tracking.
+                            isAvailable is SpatialAudioAvailabilityModel.HeadTracking
+                        } else {
+                            true
+                        }
+                    }
+                    .map { isEnabled ->
+                        val isChecked = isEnabled == currentIsEnabled
+                        val buttonViewModel: ToggleButtonViewModel =
+                            isEnabled.toViewModel(isChecked)
+                        SpatialAudioButtonViewModel(
+                            button = buttonViewModel,
+                            model = isEnabled,
+                            iconColor =
+                                Color.Attribute(
+                                    if (isChecked)
+                                        com.android.internal.R.attr.materialColorOnPrimaryContainer
+                                    else com.android.internal.R.attr.materialColorOnSurfaceVariant
+                                ),
+                            labelColor =
+                                Color.Attribute(
+                                    if (isChecked)
+                                        com.android.internal.R.attr.materialColorOnSurface
+                                    else com.android.internal.R.attr.materialColorOutline
+                                ),
+                        )
+                    }
+            }
+            .stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+    fun setEnabled(model: SpatialAudioEnabledModel) {
+        scope.launch { interactor.setEnabled(model) }
+    }
+
+    private fun SpatialAudioEnabledModel.toViewModel(isChecked: Boolean): ToggleButtonViewModel {
+        if (this is SpatialAudioEnabledModel.HeadTrackingEnabled) {
+            return ToggleButtonViewModel(
+                isChecked = isChecked,
+                icon = Icon.Resource(R.drawable.ic_head_tracking, contentDescription = null),
+                label = context.getString(R.string.volume_panel_spatial_audio_tracking)
+            )
+        }
+
+        if (this is SpatialAudioEnabledModel.SpatialAudioEnabled) {
+            return ToggleButtonViewModel(
+                isChecked = isChecked,
+                icon = Icon.Resource(R.drawable.ic_spatial_audio, contentDescription = null),
+                label = context.getString(R.string.volume_panel_spatial_audio_fixed)
+            )
+        }
+
+        if (this is SpatialAudioEnabledModel.Disabled) {
+            return ToggleButtonViewModel(
+                isChecked = isChecked,
+                icon = Icon.Resource(R.drawable.ic_spatial_audio_off, contentDescription = null),
+                label = context.getString(R.string.volume_panel_spatial_audio_off)
+            )
+        }
+
+        error("Unsupported model: $this")
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
index f31ee86..d868c33 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.volume.panel.component.bottombar.BottomBarModule
 import com.android.systemui.volume.panel.component.captioning.CaptioningModule
 import com.android.systemui.volume.panel.component.mediaoutput.MediaOutputModule
+import com.android.systemui.volume.panel.component.spatialaudio.SpatialAudioModule
 import com.android.systemui.volume.panel.component.volume.VolumeSlidersModule
 import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
@@ -49,6 +50,7 @@
             // Components modules
             BottomBarModule::class,
             AncModule::class,
+            SpatialAudioModule::class,
             VolumeSlidersModule::class,
             CaptioningModule::class,
             MediaOutputModule::class,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
index 57ea997..999f4c1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
@@ -51,6 +51,7 @@
         fun provideEnabledComponents(): Collection<VolumePanelComponentKey> {
             return setOf(
                 VolumePanelComponents.ANC,
+                VolumePanelComponents.SPATIAL_AUDIO,
                 VolumePanelComponents.CAPTIONING,
                 VolumePanelComponents.VOLUME_SLIDERS,
                 VolumePanelComponents.MEDIA_OUTPUT,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
index ec4da06..8ba06e1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
@@ -49,6 +49,7 @@
         fun provideFooterComponents(): Collection<VolumePanelComponentKey> {
             return listOf(
                 VolumePanelComponents.ANC,
+                VolumePanelComponents.SPATIAL_AUDIO,
                 VolumePanelComponents.CAPTIONING,
             )
         }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 13fb42c..90587d7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -16,8 +16,6 @@
 
 package com.android.keyguard;
 
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -32,7 +30,6 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.power.data.repository.FakePowerRepository;
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
 import com.android.systemui.res.R;
@@ -62,7 +59,6 @@
     @Mock protected KeyguardStatusViewController mControllerMock;
     @Mock protected InteractionJankMonitor mInteractionJankMonitor;
     @Mock protected ViewTreeObserver mViewTreeObserver;
-    @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     @Mock protected DumpManager mDumpManager;
     protected FakeKeyguardRepository mFakeKeyguardRepository;
     protected FakePowerRepository mFakePowerRepository;
@@ -93,7 +89,6 @@
                 mKeyguardLogger,
                 mInteractionJankMonitor,
                 deps.getKeyguardInteractor(),
-                mKeyguardTransitionInteractor,
                 mDumpManager,
                 PowerInteractorFactory.create(
                         mFakePowerRepository
@@ -110,7 +105,6 @@
 
         when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
         when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch);
-        when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow());
         when(mKeyguardStatusView.findViewById(R.id.keyguard_status_area))
                 .thenReturn(mKeyguardStatusAreaView);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index 1ce6525..eced465 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -22,11 +22,15 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.UiModeManager;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
 import android.graphics.drawable.GradientDrawable;
 import android.platform.test.annotations.EnableFlags;
@@ -54,6 +58,8 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.ArrayList;
+
 /** Tests for {@link MenuView}. */
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -73,6 +79,8 @@
     private AccessibilityManager mAccessibilityManager;
 
     private SysuiTestableContext mSpyContext;
+    @Mock
+    private PackageManager mMockPackageManager;
 
     @Before
     public void setUp() throws Exception {
@@ -82,6 +90,8 @@
 
         mSpyContext = spy(mContext);
         doNothing().when(mSpyContext).startActivity(any());
+
+        when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
         final SecureSettings secureSettings = TestUtils.mockSecureSettings();
         final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
                 secureSettings);
@@ -181,10 +191,19 @@
     @Test
     @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
     public void gotoEditScreen_sendsIntent() {
+        mockActivityQuery(true);
         mMenuView.gotoEditScreen();
         verify(mSpyContext).startActivity(any());
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+    public void gotoEditScreen_noResolve_doesNotStart() {
+        mockActivityQuery(false);
+        mMenuView.gotoEditScreen();
+        verify(mSpyContext, never()).startActivity(any());
+    }
+
     private InstantInsetLayerDrawable getMenuViewInsetLayer() {
         return (InstantInsetLayerDrawable) mMenuView.getBackground();
     }
@@ -207,4 +226,14 @@
         mUiModeManager.setNightMode(mNightMode);
         Prefs.putString(mContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, mLastPosition);
     }
+
+    private void mockActivityQuery(boolean successfulQuery) {
+        // Query just needs to return a non-empty set to be successful.
+        ArrayList<ResolveInfo> resolveInfos = new ArrayList<>();
+        if (successfulQuery) {
+            resolveInfos.add(new ResolveInfo());
+        }
+        when(mMockPackageManager.queryIntentActivities(
+                any(), any(PackageManager.ResolveInfoFlags.class))).thenReturn(resolveInfos);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
index df52265..0bd541c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
@@ -20,16 +20,19 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -43,41 +46,35 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class BurnInInteractorTest : SysuiTestCase() {
+    val kosmos = testKosmos()
+    val testScope = kosmos.testScope
+    val configurationRepository = kosmos.fakeConfigurationRepository
+    val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
+
     private val burnInOffset = 7
     private var burnInProgress = 0f
 
     @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
 
-    private lateinit var configurationRepository: FakeConfigurationRepository
-    private lateinit var keyguardInteractor: KeyguardInteractor
-    private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
-    private lateinit var testScope: TestScope
     private lateinit var underTest: BurnInInteractor
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        configurationRepository = FakeConfigurationRepository()
 
         context
             .getOrCreateTestableResources()
             .addOverride(R.dimen.burn_in_prevention_offset_y, burnInOffset)
-
-        KeyguardInteractorFactory.create().let {
-            keyguardInteractor = it.keyguardInteractor
-            fakeKeyguardRepository = it.repository
-        }
         whenever(burnInHelperWrapper.burnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset)
         setBurnInProgress(.65f)
 
-        testScope = TestScope()
         underTest =
             BurnInInteractor(
                 context,
                 burnInHelperWrapper,
-                testScope.backgroundScope,
-                configurationRepository,
-                keyguardInteractor,
+                kosmos.applicationCoroutineScope,
+                kosmos.configurationInteractor,
+                kosmos.keyguardInteractor,
             )
     }
 
@@ -122,7 +119,13 @@
         testScope.runTest {
             whenever(burnInHelperWrapper.burnInScale()).thenReturn(0.5f)
 
-            val burnInModel by collectLastValue(underTest.keyguardBurnIn)
+            val burnInModel by
+                collectLastValue(
+                    underTest.burnIn(
+                        xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
+                        yDimenResourceId = R.dimen.burn_in_prevention_offset_y
+                    )
+                )
 
             // After time tick, returns the configured values
             fakeKeyguardRepository.dozeTimeTick(10)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
index 87eee1a..0a29821 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -22,23 +22,25 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.doze.util.BurnInHelperWrapper
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -53,18 +55,19 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class UdfpsKeyguardInteractorTest : SysuiTestCase() {
+    val kosmos = testKosmos()
+    val testScope = kosmos.testScope
+    val configRepository = kosmos.fakeConfigurationRepository
+    val keyguardRepository = kosmos.fakeKeyguardRepository
+
     private val burnInProgress = 1f
     private val burnInYOffset = 20
     private val burnInXOffset = 10
 
-    private lateinit var testScope: TestScope
-    private lateinit var configRepository: FakeConfigurationRepository
     private lateinit var bouncerRepository: KeyguardBouncerRepository
-    private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var fakeCommandQueue: FakeCommandQueue
     private lateinit var burnInInteractor: BurnInInteractor
     private lateinit var shadeRepository: FakeShadeRepository
-    private lateinit var keyguardInteractor: KeyguardInteractor
     private lateinit var powerInteractor: PowerInteractor
 
     @Mock private lateinit var burnInHelper: BurnInHelperWrapper
@@ -75,12 +78,6 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        testScope = TestScope()
-        configRepository = FakeConfigurationRepository()
-        KeyguardInteractorFactory.create().let {
-            keyguardInteractor = it.keyguardInteractor
-            keyguardRepository = it.repository
-        }
         bouncerRepository = FakeKeyguardBouncerRepository()
         shadeRepository = FakeShadeRepository()
         fakeCommandQueue = FakeCommandQueue()
@@ -89,8 +86,8 @@
                 context,
                 burnInHelper,
                 testScope.backgroundScope,
-                configRepository,
-                keyguardInteractor
+                kosmos.configurationInteractor,
+                kosmos.keyguardInteractor
             )
         powerInteractor = PowerInteractorFactory.create().powerInteractor
 
@@ -98,7 +95,7 @@
             UdfpsKeyguardInteractor(
                 configRepository,
                 burnInInteractor,
-                keyguardInteractor,
+                kosmos.keyguardInteractor,
                 shadeRepository,
                 dialogManager,
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
deleted file mode 100644
index 864acfb..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.Flags as AConfigFlags
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
-
-    @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
-    @Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
-
-    private lateinit var underTest: KeyguardIndicationAreaViewModel
-    private lateinit var repository: FakeKeyguardRepository
-
-    private val startButtonFlow =
-        MutableStateFlow<KeyguardQuickAffordanceViewModel>(
-            KeyguardQuickAffordanceViewModel(
-                slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()
-            )
-        )
-    private val endButtonFlow =
-        MutableStateFlow<KeyguardQuickAffordanceViewModel>(
-            KeyguardQuickAffordanceViewModel(
-                slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId()
-            )
-        )
-    private val alphaFlow = MutableStateFlow<Float>(1f)
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
-
-        whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
-            .thenReturn(RETURNED_BURN_IN_OFFSET)
-
-        val withDeps = KeyguardInteractorFactory.create()
-        val keyguardInteractor = withDeps.keyguardInteractor
-        repository = withDeps.repository
-
-        val bottomAreaViewModel: KeyguardBottomAreaViewModel = mock()
-        whenever(bottomAreaViewModel.startButton).thenReturn(startButtonFlow)
-        whenever(bottomAreaViewModel.endButton).thenReturn(endButtonFlow)
-        whenever(bottomAreaViewModel.alpha).thenReturn(alphaFlow)
-        underTest =
-            KeyguardIndicationAreaViewModel(
-                keyguardInteractor = keyguardInteractor,
-                bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
-                keyguardBottomAreaViewModel = bottomAreaViewModel,
-                burnInHelperWrapper = burnInHelperWrapper,
-                shortcutsCombinedViewModel = shortcutsCombinedViewModel,
-                configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
-            )
-    }
-
-    @Test
-    fun alpha() = runTest {
-        val value = collectLastValue(underTest.alpha)
-
-        assertThat(value()).isEqualTo(1f)
-        alphaFlow.value = 0.1f
-        assertThat(value()).isEqualTo(0.1f)
-        alphaFlow.value = 0.5f
-        assertThat(value()).isEqualTo(0.5f)
-        alphaFlow.value = 0.2f
-        assertThat(value()).isEqualTo(0.2f)
-        alphaFlow.value = 0f
-        assertThat(value()).isEqualTo(0f)
-    }
-
-    @Test
-    fun isIndicationAreaPadded() = runTest {
-        repository.setKeyguardShowing(true)
-        val value = collectLastValue(underTest.isIndicationAreaPadded)
-
-        assertThat(value()).isFalse()
-        startButtonFlow.value = startButtonFlow.value.copy(isVisible = true)
-        assertThat(value()).isTrue()
-        endButtonFlow.value = endButtonFlow.value.copy(isVisible = true)
-        assertThat(value()).isTrue()
-        startButtonFlow.value = startButtonFlow.value.copy(isVisible = false)
-        assertThat(value()).isTrue()
-        endButtonFlow.value = endButtonFlow.value.copy(isVisible = false)
-        assertThat(value()).isFalse()
-    }
-
-    @Test
-    fun indicationAreaTranslationX() = runTest {
-        val value = collectLastValue(underTest.indicationAreaTranslationX)
-
-        assertThat(value()).isEqualTo(0f)
-        repository.setClockPosition(100, 100)
-        assertThat(value()).isEqualTo(100f)
-        repository.setClockPosition(200, 100)
-        assertThat(value()).isEqualTo(200f)
-        repository.setClockPosition(200, 200)
-        assertThat(value()).isEqualTo(200f)
-        repository.setClockPosition(300, 100)
-        assertThat(value()).isEqualTo(300f)
-    }
-
-    @Test
-    fun indicationAreaTranslationY() = runTest {
-        val value = collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET))
-
-        // Negative 0 - apparently there's a difference in floating point arithmetic - FML
-        assertThat(value()).isEqualTo(-0f)
-        val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f)
-        assertThat(value()).isEqualTo(expected1)
-        val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f)
-        assertThat(value()).isEqualTo(expected2)
-        val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f)
-        assertThat(value()).isEqualTo(expected3)
-        val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f)
-        assertThat(value()).isEqualTo(expected4)
-    }
-
-    private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
-        repository.setDozeAmount(dozeAmount)
-        return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
-    }
-
-    companion object {
-        private const val DEFAULT_BURN_IN_OFFSET = 5
-        private const val RETURNED_BURN_IN_OFFSET = 3
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index ca403e0..695d3b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -19,7 +19,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -48,7 +47,6 @@
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
-import com.android.settingslib.media.LocalMediaManager;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogTransitionAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
@@ -129,12 +127,6 @@
                 mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
-        // Using a fake package will cause routing operations to fail, so we intercept
-        // scanning-related operations.
-        mMediaOutputController.mLocalMediaManager = mock(LocalMediaManager.class);
-        doNothing().when(mMediaOutputController.mLocalMediaManager).startScan();
-        doNothing().when(mMediaOutputController.mLocalMediaManager).stopScan();
-
         mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
                 mMediaOutputController);
         mMediaOutputBaseDialogImpl.onCreate(new Bundle());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index ada93db..2e8160b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -37,7 +37,6 @@
 import com.android.systemui.model.SysUiState
 import com.android.systemui.qs.tiles.RecordIssueTile
 import com.android.systemui.res.R
-import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -71,12 +70,11 @@
     @Mock private lateinit var devicePolicyResolver: ScreenCaptureDevicePolicyResolver
     @Mock private lateinit var dprLazy: dagger.Lazy<ScreenCaptureDevicePolicyResolver>
     @Mock private lateinit var mediaProjectionMetricsLogger: MediaProjectionMetricsLogger
-    @Mock private lateinit var userContextProvider: UserContextProvider
     @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var userFileManager: UserFileManager
     @Mock private lateinit var sharedPreferences: SharedPreferences
-    @Mock private lateinit var screenCaptureDisabledDialogDelegate:
-            ScreenCaptureDisabledDialogDelegate
+    @Mock
+    private lateinit var screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate
     @Mock private lateinit var screenCaptureDisabledDialog: SystemUIDialog
 
     @Mock private lateinit var sysuiState: SysUiState
@@ -96,9 +94,8 @@
         MockitoAnnotations.initMocks(this)
         whenever(dprLazy.get()).thenReturn(devicePolicyResolver)
         whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
-        whenever(userContextProvider.userContext).thenReturn(mContext)
         whenever(screenCaptureDisabledDialogDelegate.createDialog())
-                .thenReturn(screenCaptureDisabledDialog)
+            .thenReturn(screenCaptureDisabledDialog)
         whenever(
                 userFileManager.getSharedPreferences(
                     eq(RecordIssueTile.TILE_SPEC),
@@ -123,7 +120,6 @@
         dialog =
             RecordIssueDialogDelegate(
                     factory,
-                    userContextProvider,
                     userTracker,
                     flags,
                     bgExecutor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index acbf997..cdff4d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -461,7 +461,6 @@
                 mKeyguardLogger,
                 mInteractionJankMonitor,
                 mKeyguardInteractor,
-                mKeyguardTransitionInteractor,
                 mDumpManager,
                 mPowerInteractor));
 
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 c02583a..ab28a2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -718,7 +718,8 @@
 
     @Test
     public void onPrivateProfileAdded_ignoresUntilStartComplete() {
-        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         reset(mDeviceProvisionedController);
         when(mUserManager.isManagedProfile(anyInt())).thenReturn(false);
         mBroadcastReceiver.getValue().onReceive(null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index fbefb0e..c0d3d27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -90,6 +90,7 @@
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 
+import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.colorextraction.ColorExtractor;
@@ -196,6 +197,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
 import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.onehanded.OneHandedController;
@@ -2251,6 +2253,30 @@
         verify(mBubbleController).onSensitiveNotificationProtectionStateChanged(false);
     }
 
+    @Test
+    public void setBubbleBarLocation_listenerNotified() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+        mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT);
+        assertThat(bubbleStateListener.mLastUpdate).isNotNull();
+        assertThat(bubbleStateListener.mLastUpdate.bubbleBarLocation).isEqualTo(
+                BubbleBarLocation.LEFT);
+    }
+
+    @Test
+    public void setBubbleBarLocation_barDisabled_shouldBeIgnored() {
+        mBubbleProperties.mIsBubbleBarEnabled = false;
+        mPositioner.setIsLargeScreen(true);
+
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+        mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT);
+        assertThat(bubbleStateListener.mStateChangeCalls).isEqualTo(0);
+    }
+
     /** Creates a bubble using the userId and package. */
     private Bubble createBubble(int userId, String pkg) {
         final UserHandle userHandle = new UserHandle(userId);
@@ -2436,8 +2462,15 @@
     }
 
     private static class FakeBubbleStateListener implements Bubbles.BubbleStateListener {
+
+        int mStateChangeCalls = 0;
+        @Nullable
+        BubbleBarUpdate mLastUpdate;
+
         @Override
         public void onBubbleStateChange(BubbleBarUpdate update) {
+            mStateChangeCalls++;
+            mLastUpdate = update;
         }
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 793e2d7..1e305d6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -18,7 +18,6 @@
 package com.android.systemui.keyguard.data.repository
 
 import android.graphics.Point
-import com.android.systemui.common.shared.model.Position
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -58,9 +57,6 @@
     private val _bottomAreaAlpha = MutableStateFlow(1f)
     override val bottomAreaAlpha: StateFlow<Float> = _bottomAreaAlpha
 
-    private val _clockPosition = MutableStateFlow(Position(0, 0))
-    override val clockPosition: StateFlow<Position> = _clockPosition
-
     private val _isKeyguardShowing = MutableStateFlow(false)
     override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
 
@@ -149,10 +145,6 @@
         _bottomAreaAlpha.value = alpha
     }
 
-    override fun setClockPosition(x: Int, y: Int) {
-        _clockPosition.value = Position(x, y)
-    }
-
     fun setKeyguardShowing(isShowing: Boolean) {
         _isKeyguardShowing.value = isShowing
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt
index a9d89a3..40131c7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt
@@ -19,7 +19,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.content.applicationContext
-import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.doze.util.burnInHelperWrapper
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -31,7 +31,7 @@
         context = applicationContext,
         burnInHelperWrapper = burnInHelperWrapper,
         scope = applicationCoroutineScope,
-        configurationRepository = configurationRepository,
+        configurationInteractor = configurationInteractor,
         keyguardInteractor = keyguardInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelKosmos.kt
new file mode 100644
index 0000000..b7d9676
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.deviceentry.domain.interactor.biometricMessageInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.time.systemClock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.alternateBouncerMessageAreaViewModel by
+    Kosmos.Fixture {
+        AlternateBouncerMessageAreaViewModel(
+            biometricMessageInteractor = biometricMessageInteractor,
+            alternateBouncerInteractor = alternateBouncerInteractor,
+            systemClock = systemClock,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index 75e3ac2..a863edf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
 import com.android.systemui.statusbar.phone.dozeParameters
@@ -31,6 +32,7 @@
 
 val Kosmos.keyguardRootViewModel by Fixture {
     KeyguardRootViewModel(
+        scope = applicationCoroutineScope,
         deviceEntryInteractor = deviceEntryInteractor,
         dozeParameters = dozeParameters,
         keyguardInteractor = keyguardInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt
index 0183b97..63e2d4b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt
@@ -18,23 +18,27 @@
 
 import android.media.AudioDeviceAttributes
 import com.android.settingslib.media.data.repository.SpatializerRepository
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
 
 class FakeSpatializerRepository : SpatializerRepository {
 
     var defaultSpatialAudioAvailable: Boolean = false
+    var defaultHeadTrackingAvailable: Boolean = false
 
     private val spatialAudioAvailabilityByDevice: MutableMap<AudioDeviceAttributes, Boolean> =
         mutableMapOf()
+    private val headTrackingAvailabilityByDevice: MutableMap<AudioDeviceAttributes, Boolean> =
+        mutableMapOf()
     private val spatialAudioCompatibleDevices: MutableList<AudioDeviceAttributes> = mutableListOf()
 
-    private val mutableHeadTrackingAvailable = MutableStateFlow(false)
     private val headTrackingEnabledByDevice = mutableMapOf<AudioDeviceAttributes, Boolean>()
 
-    override val isHeadTrackingAvailable: StateFlow<Boolean> =
-        mutableHeadTrackingAvailable.asStateFlow()
+    override suspend fun isHeadTrackingAvailableForDevice(
+        audioDeviceAttributes: AudioDeviceAttributes
+    ): Boolean =
+        headTrackingAvailabilityByDevice.getOrDefault(
+            audioDeviceAttributes,
+            defaultHeadTrackingAvailable
+        )
 
     override suspend fun isSpatialAudioAvailableForDevice(
         audioDeviceAttributes: AudioDeviceAttributes
@@ -77,7 +81,10 @@
         spatialAudioAvailabilityByDevice[audioDeviceAttributes] = isAvailable
     }
 
-    fun setIsHeadTrackingAvailable(isAvailable: Boolean) {
-        mutableHeadTrackingAvailable.value = isAvailable
+    fun setIsHeadTrackingAvailable(
+        audioDeviceAttributes: AudioDeviceAttributes,
+        isAvailable: Boolean,
+    ) {
+        headTrackingAvailabilityByDevice[audioDeviceAttributes] = isAvailable
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index 4fc65bf..be2ad21 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -1601,6 +1601,9 @@
                                 + " pointers down.");
                 return;
             }
+            if (Flags.resetHoverEventTimerOnActionUp() && mEvents.size() == 0) {
+                return;
+            }
             // Send an accessibility event to announce the touch exploration start.
             mDispatcher.sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START);
             if (isSendMotionEventsEnabled()) {
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index fbd6709..1749ee3 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -2450,7 +2450,8 @@
         AppWidgetProviderInfo info = createPartialProviderInfo(providerId, ri, existing);
 
         if (android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.disablePrivateSpaceItemsOnHome()) {
+                && android.multiuser.Flags.disablePrivateSpaceItemsOnHome()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             // Do not add widget providers for profiles with items restricted on home screen.
             if (info != null && mUserManager
                     .getUserProperties(info.getProfile()).areItemsRestrictedOnHomeScreen()) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 3abfe082..13a1807 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1419,7 +1419,8 @@
 
     private boolean allowBiometricUnlockForPrivateProfile() {
         return android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace();
+                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures();
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index b2a738f..3d95fee 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -403,15 +403,14 @@
             brightnessEvent.setRecommendedBrightness(mScreenAutoBrightness);
             brightnessEvent.setFlags(brightnessEvent.getFlags()
                     | (!mAmbientLuxValid ? BrightnessEvent.FLAG_INVALID_LUX : 0)
-                    | (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE
-                        ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
+                    | (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
             brightnessEvent.setAutoBrightnessMode(getMode());
         }
 
         if (!mAmbientLuxValid) {
             return PowerManager.BRIGHTNESS_INVALID_FLOAT;
         }
-        if (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE) {
+        if (shouldApplyDozeScaleFactor()) {
             return mScreenAutoBrightness * mDozeScaleFactor;
         }
         return mScreenAutoBrightness;
@@ -434,7 +433,7 @@
 
         float brightness = mCurrentBrightnessMapper.getBrightness(mLastObservedLux,
                 mForegroundAppPackageName, mForegroundAppCategory);
-        if (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE) {
+        if (shouldApplyDozeScaleFactor()) {
             brightness *= mDozeScaleFactor;
         }
 
@@ -443,8 +442,7 @@
             brightnessEvent.setRecommendedBrightness(brightness);
             brightnessEvent.setFlags(brightnessEvent.getFlags()
                     | (mLastObservedLux == INVALID_LUX ? BrightnessEvent.FLAG_INVALID_LUX : 0)
-                    | (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE
-                    ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
+                    | (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
             brightnessEvent.setAutoBrightnessMode(getMode());
         }
         return brightness;
@@ -1263,6 +1261,12 @@
         }
     }
 
+    private boolean shouldApplyDozeScaleFactor() {
+        // Don't apply the doze scale factor if we have a designated brightness curve for doze
+        return mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE
+                && getMode() != AUTO_BRIGHTNESS_MODE_DOZE;
+    }
+
     private class ShortTermModel {
         // When the short term model is invalidated, we don't necessarily reset it (i.e. clear the
         // user's adjustment) immediately, but wait for a drastic enough change in the ambient
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index 91e560e..b1defe9 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -113,4 +113,14 @@
             Trace.traceEnd(Trace.TRACE_TAG_POWER);
         }
     }
+
+    @Override
+    public float getBrightness() {
+        return mDisplayPowerController.getScreenBrightnessSetting();
+    }
+
+    @Override
+    public float getDozeBrightness() {
+        return mDisplayPowerController.getDozeBrightnessForOffload();
+    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 7f014f6..77a43d0 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -487,6 +487,9 @@
 
     private DisplayOffloadSession mDisplayOffloadSession;
 
+    // Used to scale the brightness in doze mode
+    private float mDozeScaleFactor;
+
     /**
      * Creates the display power controller.
      */
@@ -547,6 +550,9 @@
         loadBrightnessRampRates();
         mSkipScreenOnBrightnessRamp = resources.getBoolean(
                 R.bool.config_skipScreenOnBrightnessRamp);
+        mDozeScaleFactor = context.getResources().getFraction(
+                R.fraction.config_screenAutoBrightnessDozeScaleFactor,
+                1, 1);
 
         Runnable modeChangeCallback = () -> {
             sendUpdatePowerState();
@@ -1042,10 +1048,6 @@
         }
 
         if (defaultModeBrightnessMapper != null) {
-            final float dozeScaleFactor = context.getResources().getFraction(
-                    R.fraction.config_screenAutoBrightnessDozeScaleFactor,
-                    1, 1);
-
             // Ambient Lux - Active Mode Brightness Thresholds
             float[] ambientBrighteningThresholds =
                     mDisplayDeviceConfig.getAmbientBrighteningPercentages();
@@ -1156,7 +1158,7 @@
             mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController(
                     this, handler.getLooper(), mSensorManager, mLightSensor,
                     brightnessMappers, lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN,
-                    PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate,
+                    PowerManager.BRIGHTNESS_MAX, mDozeScaleFactor, lightSensorRate,
                     initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
                     brighteningLightDebounceIdle, darkeningLightDebounceIdle,
                     autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
@@ -1473,17 +1475,22 @@
             mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
         }
 
-        // If there's an offload session and auto-brightness is on, we need to set the initial doze
-        // brightness using the doze auto-brightness curve before the offload session starts
-        // controlling the brightness.
-        if (Float.isNaN(brightnessState) && mFlags.areAutoBrightnessModesEnabled()
-                && mFlags.isDisplayOffloadEnabled()
-                && mPowerRequest.policy == POLICY_DOZE
-                && mDisplayOffloadSession != null
-                && mAutomaticBrightnessController != null
-                && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
-            rawBrightnessState = mAutomaticBrightnessController
-                    .getAutomaticScreenBrightnessBasedOnLastObservedLux(mTempBrightnessEvent);
+        // If there's an offload session, we need to set the initial doze brightness before
+        // the offload session starts controlling the brightness.
+        if (Float.isNaN(brightnessState) && mFlags.isDisplayOffloadEnabled()
+                && mPowerRequest.policy == POLICY_DOZE && mDisplayOffloadSession != null) {
+            if (mAutomaticBrightnessController != null
+                    && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
+                // Use the auto-brightness curve and the last observed lux
+                rawBrightnessState = mAutomaticBrightnessController
+                        .getAutomaticScreenBrightnessBasedOnLastObservedLux(
+                                mTempBrightnessEvent);
+            } else {
+                rawBrightnessState = getDozeBrightnessForOffload();
+                mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
+                        | BrightnessEvent.FLAG_DOZE_SCALE);
+            }
+
             if (BrightnessUtils.isValidBrightnessValue(rawBrightnessState)) {
                 brightnessState = clampScreenBrightness(rawBrightnessState);
                 mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_INITIAL);
@@ -2444,6 +2451,11 @@
     }
 
     @Override
+    public float getDozeBrightnessForOffload() {
+        return mDisplayBrightnessController.getCurrentBrightness() * mDozeScaleFactor;
+    }
+
+    @Override
     public void setBrightness(float brightness) {
         mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness));
     }
@@ -2596,6 +2608,7 @@
         }
         pw.println("  mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
         pw.println("  mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
+        pw.println("  mDozeScaleFactor=" + mDozeScaleFactor);
         mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index ecf1635..408d610 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -161,6 +161,11 @@
     float getScreenBrightnessSetting();
 
     /**
+     * Gets the brightness value used when the device is in doze
+     */
+    float getDozeBrightnessForOffload();
+
+    /**
      * Sets up the temporary brightness for the associated display
      */
     void setTemporaryBrightness(float brightness);
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 71cc872..03fb147 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -71,8 +71,14 @@
     @Nullable
     private final OffloadBrightnessStrategy mOffloadBrightnessStrategy;
 
+    // A collective representation of all the strategies that the selector is aware of. This is
+    // non null, but the strategies this is tracking can be null
+    @NonNull
     private final DisplayBrightnessStrategy[] mDisplayBrightnessStrategies;
 
+    @NonNull
+    private final DisplayManagerFlags mDisplayManagerFlags;
+
     // We take note of the old brightness strategy so that we can know when the strategy changes.
     private String mOldBrightnessStrategyName;
 
@@ -86,6 +92,7 @@
         if (injector == null) {
             injector = new Injector();
         }
+        mDisplayManagerFlags = flags;
         mDisplayId = displayId;
         mDozeBrightnessStrategy = injector.getDozeBrightnessStrategy();
         mScreenOffBrightnessStrategy = injector.getScreenOffBrightnessStrategy();
@@ -139,6 +146,10 @@
             displayBrightnessStrategy = mOffloadBrightnessStrategy;
         }
 
+        if (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) {
+            postProcess(constructStrategySelectionNotifyRequest(displayBrightnessStrategy));
+        }
+
         if (!mOldBrightnessStrategyName.equals(displayBrightnessStrategy.getName())) {
             Slog.i(TAG,
                     "Changing the DisplayBrightnessStrategy from " + mOldBrightnessStrategyName
@@ -186,13 +197,27 @@
                 "  mAllowAutoBrightnessWhileDozingConfig= "
                         + mAllowAutoBrightnessWhileDozingConfig);
         IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
-        for (DisplayBrightnessStrategy displayBrightnessStrategy: mDisplayBrightnessStrategies) {
+        for (DisplayBrightnessStrategy displayBrightnessStrategy : mDisplayBrightnessStrategies) {
             if (displayBrightnessStrategy != null) {
                 displayBrightnessStrategy.dump(ipw);
             }
         }
     }
 
+    private StrategySelectionNotifyRequest constructStrategySelectionNotifyRequest(
+            DisplayBrightnessStrategy selectedDisplayBrightnessStrategy) {
+        return new StrategySelectionNotifyRequest(selectedDisplayBrightnessStrategy);
+    }
+
+    private void postProcess(StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        for (DisplayBrightnessStrategy displayBrightnessStrategy : mDisplayBrightnessStrategies) {
+            if (displayBrightnessStrategy != null) {
+                displayBrightnessStrategy.strategySelectionPostProcessor(
+                        strategySelectionNotifyRequest);
+            }
+        }
+    }
+
     /**
      * Validates if the conditions are met to qualify for the DozeBrightnessStrategy.
      */
diff --git a/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
new file mode 100644
index 0000000..d8bd2e4
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness;
+
+import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
+
+import java.util.Objects;
+
+/**
+ * A wrapper class to encapsulate the request to notify the strategies about the selection of a
+ * DisplayBrightnessStrategy
+ */
+public final class StrategySelectionNotifyRequest {
+    // The strategy that was selected with the current request
+    private final DisplayBrightnessStrategy mSelectedDisplayBrightnessStrategy;
+
+    public StrategySelectionNotifyRequest(DisplayBrightnessStrategy displayBrightnessStrategy) {
+        mSelectedDisplayBrightnessStrategy = displayBrightnessStrategy;
+    }
+
+    public DisplayBrightnessStrategy getSelectedDisplayBrightnessStrategy() {
+        return mSelectedDisplayBrightnessStrategy;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof StrategySelectionNotifyRequest)) {
+            return false;
+        }
+        StrategySelectionNotifyRequest other = (StrategySelectionNotifyRequest) obj;
+        return other.getSelectedDisplayBrightnessStrategy()
+                == getSelectedDisplayBrightnessStrategy();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSelectedDisplayBrightnessStrategy);
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
index 9ee1d73..11edde9 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
@@ -22,6 +22,7 @@
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
 
 import java.io.PrintWriter;
 
@@ -53,4 +54,10 @@
 
     @Override
     public void dump(PrintWriter writer) {}
+
+    @Override
+    public void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        // DO NOTHING
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
index 1f28eb4..7b49957 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
@@ -20,6 +20,7 @@
 import android.hardware.display.DisplayManagerInternal;
 
 import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
 
 import java.io.PrintWriter;
 
@@ -48,4 +49,10 @@
      * @param writer
      */
     void dump(PrintWriter writer);
+
+     /**
+     * Notifies this strategy about the selection of a DisplayBrightnessStrategy
+     */
+    void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest);
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
index 2be7443..5afdc42 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
@@ -21,6 +21,7 @@
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
 
 import java.io.PrintWriter;
 
@@ -46,4 +47,10 @@
 
     @Override
     public void dump(PrintWriter writer) {}
+
+    @Override
+    public void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        // DO NOTHING
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
index 54f9afc..0650c1c 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
@@ -22,6 +22,7 @@
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
 
 import java.io.PrintWriter;
 
@@ -82,4 +83,10 @@
         writer.println("  mBrightnessToFollow:" + mBrightnessToFollow);
         writer.println("  mBrightnessToFollowSlowChange:" + mBrightnessToFollowSlowChange);
     }
+
+    @Override
+    public void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        // DO NOTHING
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
index 49c3e03..bf37ee0 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
@@ -22,6 +22,7 @@
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
 
 import java.io.PrintWriter;
 
@@ -44,4 +45,10 @@
 
     @Override
     public void dump(PrintWriter writer) {}
+
+    @Override
+    public void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        // DO NOTHING
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
index 4ffb16b..d2bb1e2 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
@@ -21,6 +21,7 @@
 
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
 
 import java.io.PrintWriter;
 
@@ -72,4 +73,10 @@
         writer.println("OffloadBrightnessStrategy:");
         writer.println("  mOffloadScreenBrightness:" + mOffloadScreenBrightness);
     }
+
+    @Override
+    public void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        // DO NOTHING
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
index 7b651d8..653170c 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
@@ -21,6 +21,7 @@
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
 
 import java.io.PrintWriter;
 
@@ -45,4 +46,10 @@
 
     @Override
     public void dump(PrintWriter writer) {}
+
+    @Override
+    public void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        // DO NOTHING
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
index 201ef41..f0cce23 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
@@ -22,6 +22,7 @@
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
 
 import java.io.PrintWriter;
 
@@ -46,4 +47,10 @@
 
     @Override
     public void dump(PrintWriter writer) {}
+
+    @Override
+    public void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        // DO NOTHING
+    }
 }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
index bbd0c00..91e1d09 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
@@ -22,6 +22,7 @@
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
 
 import java.io.PrintWriter;
 
@@ -73,4 +74,10 @@
         writer.println("TemporaryBrightnessStrategy:");
         writer.println("  mTemporaryScreenBrightness:" + mTemporaryScreenBrightness);
     }
+
+    @Override
+    public void strategySelectionPostProcessor(
+            StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+        // DO NOTHING
+    }
 }
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 516d4b1..3c98ee4 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -126,6 +126,13 @@
             Flags::sensorBasedBrightnessThrottling
     );
 
+
+    private final FlagState mRefactorDisplayPowerController = new FlagState(
+            Flags.FLAG_REFACTOR_DISPLAY_POWER_CONTROLLER,
+            Flags::refactorDisplayPowerController
+    );
+
+
     /**
      * @return {@code true} if 'port' is allowed in display layout configuration file.
      */
@@ -256,6 +263,10 @@
         return mSensorBasedBrightnessThrottling.isEnabled();
     }
 
+    public boolean isRefactorDisplayPowerControllerEnabled() {
+        return mRefactorDisplayPowerController.isEnabled();
+    }
+
     /**
      * dumps all flagstates
      * @param pw printWriter
@@ -280,6 +291,7 @@
         pw.println(" " + mFastHdrTransitions);
         pw.println(" " + mRefreshRateVotingTelemetry);
         pw.println(" " + mSensorBasedBrightnessThrottling);
+        pw.println(" " + mRefactorDisplayPowerController);
     }
 
     private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 63ab3a9..3404527 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -192,3 +192,11 @@
     bug: "294900859"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "refactor_display_power_controller"
+    namespace: "display_manager"
+    description: "Feature flag for refactoring   the DisplayPowerController and associated components"
+    bug: "294444204"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
new file mode 100644
index 0000000..c7b60da
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -0,0 +1,106 @@
+/*
+ * 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+/**
+ * Provides accesses to per-user additional {@link android.view.inputmethod.InputMethodSubtype}
+ * persistent storages.
+ */
+final class AdditionalSubtypeMapRepository {
+    @GuardedBy("ImfLock.class")
+    @NonNull
+    private static final SparseArray<AdditionalSubtypeMap> sPerUserMap = new SparseArray<>();
+
+    /**
+     * Not intended to be instantiated.
+     */
+    private AdditionalSubtypeMapRepository() {
+    }
+
+    @NonNull
+    @GuardedBy("ImfLock.class")
+    static AdditionalSubtypeMap get(@UserIdInt int userId) {
+        final AdditionalSubtypeMap map = sPerUserMap.get(userId);
+        if (map != null) {
+            return map;
+        }
+        final AdditionalSubtypeMap newMap = AdditionalSubtypeUtils.load(userId);
+        sPerUserMap.put(userId, newMap);
+        return newMap;
+    }
+
+    @GuardedBy("ImfLock.class")
+    static void putAndSave(@UserIdInt int userId, @NonNull AdditionalSubtypeMap map,
+            @NonNull InputMethodMap inputMethodMap) {
+        final AdditionalSubtypeMap previous = sPerUserMap.get(userId);
+        if (previous == map) {
+            return;
+        }
+        sPerUserMap.put(userId, map);
+        // TODO: Offload this to a background thread.
+        // TODO: Skip if the previous data is exactly the same as new one.
+        AdditionalSubtypeUtils.save(map, inputMethodMap, userId);
+    }
+
+    static void initialize(@NonNull Handler handler) {
+        final UserManagerInternal userManagerInternal =
+                LocalServices.getService(UserManagerInternal.class);
+        handler.post(() -> {
+            userManagerInternal.addUserLifecycleListener(
+                    new UserManagerInternal.UserLifecycleListener() {
+                        @Override
+                        public void onUserCreated(UserInfo user, @Nullable Object token) {
+                            final int userId = user.id;
+                            handler.post(() -> {
+                                synchronized (ImfLock.class) {
+                                    if (!sPerUserMap.contains(userId)) {
+                                        sPerUserMap.put(userId,
+                                                AdditionalSubtypeUtils.load(userId));
+                                    }
+                                }
+                            });
+                        }
+
+                        @Override
+                        public void onUserRemoved(UserInfo user) {
+                            final int userId = user.id;
+                            handler.post(() -> {
+                                synchronized (ImfLock.class) {
+                                    sPerUserMap.remove(userId);
+                                }
+                            });
+                        }
+                    });
+            synchronized (ImfLock.class) {
+                for (int userId : userManagerInternal.getUserIds()) {
+                    sPerUserMap.put(userId, AdditionalSubtypeUtils.load(userId));
+                }
+            }
+        });
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2205986..251e69f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -192,7 +192,9 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
 import java.security.InvalidParameterException;
 import java.time.Instant;
 import java.time.ZoneId;
@@ -230,6 +232,16 @@
         int FAILURE = -1;
     }
 
+    /**
+     * Indicates that the annotated field is not yet ready for concurrent multi-user support.
+     *
+     * <p>See b/305849394 for details.</p>
+     */
+    @Retention(SOURCE)
+    @Target({ElementType.FIELD})
+    private @interface MultiUserUnawareField {
+    }
+
     private static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1;
 
     private static final int MSG_HIDE_ALL_INPUT_METHODS = 1035;
@@ -279,7 +291,9 @@
     final Resources mRes;
     private final Handler mHandler;
     @NonNull
+    @MultiUserUnawareField
     private InputMethodSettings mSettings;
+    @MultiUserUnawareField
     final SettingsObserver mSettingsObserver;
     final WindowManagerInternal mWindowManagerInternal;
     private final ActivityManagerInternal mActivityManagerInternal;
@@ -288,15 +302,16 @@
     final ImePlatformCompatUtils mImePlatformCompatUtils;
     final InputMethodDeviceConfigs mInputMethodDeviceConfigs;
 
-    @GuardedBy("ImfLock.class")
-    @NonNull
-    private AdditionalSubtypeMap mAdditionalSubtypeMap = AdditionalSubtypeMap.EMPTY_MAP;
     private final UserManagerInternal mUserManagerInternal;
+    @MultiUserUnawareField
     private final InputMethodMenuController mMenuController;
+    @MultiUserUnawareField
     @NonNull private final InputMethodBindingController mBindingController;
+    @MultiUserUnawareField
     @NonNull private final AutofillSuggestionsController mAutofillController;
 
     @GuardedBy("ImfLock.class")
+    @MultiUserUnawareField
     @NonNull private final ImeVisibilityStateComputer mVisibilityStateComputer;
 
     @GuardedBy("ImfLock.class")
@@ -315,13 +330,16 @@
 
     // Mapping from deviceId to the device-specific imeId for that device.
     @GuardedBy("ImfLock.class")
+    @MultiUserUnawareField
     private final SparseArray<String> mVirtualDeviceMethodMap = new SparseArray<>();
 
     // TODO: Instantiate mSwitchingController for each user.
     @NonNull
+    @MultiUserUnawareField
     private InputMethodSubtypeSwitchingController mSwitchingController;
     // TODO: Instantiate mHardwareKeyboardShortcutController for each user.
     @NonNull
+    @MultiUserUnawareField
     private HardwareKeyboardShortcutController mHardwareKeyboardShortcutController;
 
     /**
@@ -339,23 +357,29 @@
     }
 
     @GuardedBy("ImfLock.class")
+    @MultiUserUnawareField
     private int mDisplayIdToShowIme = INVALID_DISPLAY;
 
     @GuardedBy("ImfLock.class")
+    @MultiUserUnawareField
     private int mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
 
     @Nullable private StatusBarManagerInternal mStatusBarManagerInternal;
     private boolean mShowOngoingImeSwitcherForPhones;
     @GuardedBy("ImfLock.class")
+    @MultiUserUnawareField
     private final HandwritingModeController mHwController;
     @GuardedBy("ImfLock.class")
+    @MultiUserUnawareField
     private IntArray mStylusIds;
 
     @GuardedBy("ImfLock.class")
     @Nullable
+    @MultiUserUnawareField
     private OverlayableSystemBooleanResourceWrapper mImeDrawsImeNavBarRes;
     @GuardedBy("ImfLock.class")
     @Nullable
+    @MultiUserUnawareField
     Future<?> mImeDrawsImeNavBarResLazyInitFuture;
 
     static class SessionState {
@@ -420,6 +444,7 @@
     /**
      * Holds the current IME binding state info.
      */
+    @MultiUserUnawareField
     ImeBindingState mImeBindingState;
 
     /**
@@ -486,27 +511,32 @@
      * upon reports from the input method.  If the window state is already changed before the report
      * is handled, this field just keeps the last value.
      */
+    @MultiUserUnawareField
     IBinder mLastImeTargetWindow;
 
     /**
      * The {@link IRemoteInputConnection} last provided by the current client.
      */
+    @MultiUserUnawareField
     IRemoteInputConnection mCurInputConnection;
 
     /**
      * The {@link ImeOnBackInvokedDispatcher} last provided by the current client to
      * receive {@link android.window.OnBackInvokedCallback}s forwarded from IME.
      */
+    @MultiUserUnawareField
     ImeOnBackInvokedDispatcher mCurImeDispatcher;
 
     /**
      * The {@link IRemoteAccessibilityInputConnection} last provided by the current client.
      */
+    @MultiUserUnawareField
     @Nullable IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
 
     /**
      * The {@link EditorInfo} last provided by the current client.
      */
+    @MultiUserUnawareField
     @Nullable
     EditorInfo mCurEditorInfo;
 
@@ -527,11 +557,13 @@
     /**
      * The current subtype of the current input method.
      */
+    @MultiUserUnawareField
     private InputMethodSubtype mCurrentSubtype;
 
     /**
      * {@code true} if the IME has not been mostly hidden via {@link android.view.InsetsController}
      */
+    @MultiUserUnawareField
     private boolean mCurPerceptible;
 
     /**
@@ -548,11 +580,13 @@
      * otherwise {@code null}.
      */
     @Nullable
+    @MultiUserUnawareField
     private ImeTracker.Token mCurStatsToken;
 
     /**
      * {@code true} if the current input method is in fullscreen mode.
      */
+    @MultiUserUnawareField
     boolean mInFullscreenMode;
 
     /**
@@ -588,6 +622,7 @@
     }
 
     @GuardedBy("ImfLock.class")
+    @MultiUserUnawareField
     private int mCurTokenDisplayId = INVALID_DISPLAY;
 
     /**
@@ -595,6 +630,7 @@
      */
     @GuardedBy("ImfLock.class")
     @Nullable
+    @MultiUserUnawareField
     private IBinder mCurHostInputToken;
 
     /**
@@ -633,25 +669,31 @@
     /**
      * Have we called mCurMethod.bindInput()?
      */
+    @MultiUserUnawareField
     boolean mBoundToMethod;
 
     /**
      * Have we called bindInput() for accessibility services?
      */
+    @MultiUserUnawareField
     boolean mBoundToAccessibility;
 
     /**
      * Currently enabled session.
      */
     @GuardedBy("ImfLock.class")
+    @MultiUserUnawareField
     SessionState mEnabledSession;
+    @MultiUserUnawareField
     SparseArray<AccessibilitySessionState> mEnabledAccessibilitySessions = new SparseArray<>();
 
     /**
      * True if the device is currently interactive with user.  The value is true initially.
      */
+    @MultiUserUnawareField
     boolean mIsInteractive = true;
 
+    @MultiUserUnawareField
     int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
 
     /**
@@ -675,6 +717,7 @@
      * <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and
      * {@link InputMethodBindingController#unbindCurrentMethod()}.</em>
      */
+    @MultiUserUnawareField
     int mImeWindowVis;
 
     private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
@@ -1310,13 +1353,12 @@
             final int userId = getChangingUserId();
             synchronized (ImfLock.class) {
                 final boolean isCurrentUser = (userId == mSettings.getUserId());
-                final AdditionalSubtypeMap additionalSubtypeMap;
+                final AdditionalSubtypeMap additionalSubtypeMap =
+                        AdditionalSubtypeMapRepository.get(userId);
                 final InputMethodSettings settings;
                 if (isCurrentUser) {
-                    additionalSubtypeMap = mAdditionalSubtypeMap;
                     settings = mSettings;
                 } else {
-                    additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
                     settings = queryInputMethodServicesInternal(mContext, userId,
                             additionalSubtypeMap, DirectBootAwareness.AUTO);
                 }
@@ -1331,11 +1373,8 @@
                 final AdditionalSubtypeMap newMap =
                         additionalSubtypeMap.cloneWithRemoveOrSelf(changedImes);
                 if (newMap != additionalSubtypeMap) {
-                    if (isCurrentUser) {
-                        mAdditionalSubtypeMap = newMap;
-                    }
-                    AdditionalSubtypeUtils.save(
-                            newMap, settings.getMethodMap(), settings.getUserId());
+                    AdditionalSubtypeMapRepository.putAndSave(userId, newMap,
+                            settings.getMethodMap());
                 }
                 if (!changedImes.isEmpty()) {
                     mChangedPackages.add(packageName);
@@ -1382,13 +1421,12 @@
             synchronized (ImfLock.class) {
                 final int userId = getChangingUserId();
                 final boolean isCurrentUser = (userId == mSettings.getUserId());
-                AdditionalSubtypeMap additionalSubtypeMap;
+                AdditionalSubtypeMap additionalSubtypeMap =
+                        AdditionalSubtypeMapRepository.get(userId);
                 final InputMethodSettings settings;
                 if (isCurrentUser) {
-                    additionalSubtypeMap = mAdditionalSubtypeMap;
                     settings = mSettings;
                 } else {
-                    additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
                     settings = queryInputMethodServicesInternal(mContext, userId,
                             additionalSubtypeMap, DirectBootAwareness.AUTO);
                 }
@@ -1419,11 +1457,8 @@
                                 + imi.getComponent());
                         additionalSubtypeMap =
                                 additionalSubtypeMap.cloneWithRemoveOrSelf(imi.getId());
-                        AdditionalSubtypeUtils.save(additionalSubtypeMap,
-                                settings.getMethodMap(), userId);
-                        if (isCurrentUser) {
-                            mAdditionalSubtypeMap = additionalSubtypeMap;
-                        }
+                        AdditionalSubtypeMapRepository.putAndSave(userId,
+                                additionalSubtypeMap, settings.getMethodMap());
                     }
                 }
 
@@ -1515,6 +1550,7 @@
      */
     @Nullable
     @GuardedBy("ImfLock.class")
+    @MultiUserUnawareField
     private UserSwitchHandlerTask mUserSwitchHandlerTask;
 
     /**
@@ -1658,12 +1694,13 @@
 
         mShowOngoingImeSwitcherForPhones = false;
 
+        AdditionalSubtypeMapRepository.initialize(mHandler);
+
         final int userId = mActivityManagerInternal.getCurrentUserId();
 
         // mSettings should be created before buildInputMethodListLocked
         mSettings = InputMethodSettings.createEmptyMap(userId);
 
-        mAdditionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
         mSwitchingController =
                 InputMethodSubtypeSwitchingController.createInstanceLocked(context,
                         mSettings.getMethodMap(), userId);
@@ -1808,8 +1845,6 @@
         mSettingsObserver.registerContentObserverLocked(newUserId);
 
         mSettings = InputMethodSettings.createEmptyMap(newUserId);
-        // Additional subtypes should be reset when the user is changed
-        mAdditionalSubtypeMap = AdditionalSubtypeUtils.load(newUserId);
         final String defaultImiId = mSettings.getSelectedInputMethod();
 
         if (DEBUG) {
@@ -1897,7 +1932,7 @@
                     }
                 }, "Lazily initialize IMMS#mImeDrawsImeNavBarRes");
 
-                mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
+                mMyPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
                 mSettingsObserver.registerContentObserverLocked(currentUserId);
 
                 final IntentFilter broadcastFilterForAllUsers = new IntentFilter();
@@ -2017,7 +2052,7 @@
             }
             //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
             //TODO(b/210039666): use cache.
-            final InputMethodSettings settings = queryMethodMapForUser(userId);
+            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
             final InputMethodInfo imi = settings.getMethodMap().get(
                     settings.getSelectedInputMethod());
             return imi != null && imi.supportsStylusHandwriting()
@@ -2045,7 +2080,8 @@
                 && directBootAwareness == DirectBootAwareness.AUTO) {
             settings = mSettings;
         } else {
-            final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
+            final AdditionalSubtypeMap additionalSubtypeMap =
+                    AdditionalSubtypeMapRepository.get(userId);
             settings = queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
                     directBootAwareness);
         }
@@ -2066,7 +2102,7 @@
             methodList = mSettings.getEnabledInputMethodList();
             settings = mSettings;
         } else {
-            settings = queryMethodMapForUser(userId);
+            settings = queryMethodMapForUserLocked(userId);
             methodList = settings.getEnabledInputMethodList();
         }
         // filter caller's access to input methods
@@ -2141,7 +2177,7 @@
             return mSettings.getEnabledInputMethodSubtypeList(
                     imi, allowsImplicitlyEnabledSubtypes);
         }
-        final InputMethodSettings settings = queryMethodMapForUser(userId);
+        final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
         final InputMethodInfo imi = settings.getMethodMap().get(imiId);
         if (imi == null) {
             return Collections.emptyList();
@@ -4307,7 +4343,7 @@
                 return mSettings.getLastInputMethodSubtype();
             }
 
-            final InputMethodSettings settings = queryMethodMapForUser(userId);
+            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
             return settings.getLastInputMethodSubtype();
         }
     }
@@ -4338,33 +4374,25 @@
                 return;
             }
 
-            if (mSettings.getUserId() == userId) {
-                final var newAdditionalSubtypeMap = mSettings.getNewAdditionalSubtypeMap(
-                        imiId, toBeAdded, mAdditionalSubtypeMap, mPackageManagerInternal,
-                        callingUid);
-                if (mAdditionalSubtypeMap == newAdditionalSubtypeMap) {
-                    return;
-                }
-                AdditionalSubtypeUtils.save(newAdditionalSubtypeMap, mSettings.getMethodMap(),
-                        mSettings.getUserId());
-                mAdditionalSubtypeMap = newAdditionalSubtypeMap;
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
-                }
-                return;
-            }
-
-            final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
-            final InputMethodSettings settings = queryInputMethodServicesInternal(mContext, userId,
-                    additionalSubtypeMap, DirectBootAwareness.AUTO);
+            final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
+            final boolean isCurrentUser = (mSettings.getUserId() == userId);
+            final InputMethodSettings settings = isCurrentUser
+                    ? mSettings
+                    : queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
+                            DirectBootAwareness.AUTO);
             final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
                     imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
             if (additionalSubtypeMap != newAdditionalSubtypeMap) {
-                AdditionalSubtypeUtils.save(newAdditionalSubtypeMap, settings.getMethodMap(),
-                        settings.getUserId());
+                AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
+                        settings.getMethodMap());
+                if (isCurrentUser) {
+                    final long ident = Binder.clearCallingIdentity();
+                    try {
+                        buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
+                    }
+                }
             }
         }
     }
@@ -4391,7 +4419,7 @@
             synchronized (ImfLock.class) {
                 final boolean currentUser = (mSettings.getUserId() == userId);
                 final InputMethodSettings settings = currentUser
-                        ? mSettings : queryMethodMapForUser(userId);
+                        ? mSettings : queryMethodMapForUserLocked(userId);
                 if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
                     return;
                 }
@@ -5293,7 +5321,8 @@
         mMyPackageMonitor.clearKnownImePackageNamesLocked();
 
         mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(),
-                mAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+                AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
+                DirectBootAwareness.AUTO);
 
         // Construct the set of possible IME packages for onPackageChanged() to avoid false
         // negatives when the package state remains to be the same but only the component state is
@@ -5565,7 +5594,7 @@
                 return getCurrentInputMethodSubtypeLocked();
             }
 
-            final InputMethodSettings settings = queryMethodMapForUser(userId);
+            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
             return settings.getCurrentInputMethodSubtypeForNonCurrentUsers();
         }
     }
@@ -5632,15 +5661,18 @@
         if (userId == mSettings.getUserId()) {
             settings = mSettings;
         } else {
-            final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
+            final AdditionalSubtypeMap additionalSubtypeMap =
+                    AdditionalSubtypeMapRepository.get(userId);
             settings = queryInputMethodServicesInternal(mContext, userId,
                     additionalSubtypeMap, DirectBootAwareness.AUTO);
         }
         return settings.getMethodMap().get(settings.getSelectedInputMethod());
     }
 
-    private InputMethodSettings queryMethodMapForUser(@UserIdInt int userId) {
-        final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
+    @GuardedBy("ImfLock.class")
+    private InputMethodSettings queryMethodMapForUserLocked(@UserIdInt int userId) {
+        final AdditionalSubtypeMap additionalSubtypeMap =
+                AdditionalSubtypeMapRepository.get(userId);
         return queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
                 DirectBootAwareness.AUTO);
     }
@@ -5656,7 +5688,7 @@
             setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
             return true;
         }
-        final InputMethodSettings settings = queryMethodMapForUser(userId);
+        final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
         if (!settings.getMethodMap().containsKey(imeId)
                 || !settings.getEnabledInputMethodList().contains(
                         settings.getMethodMap().get(imeId))) {
@@ -5796,7 +5828,7 @@
                     setInputMethodEnabledLocked(imeId, enabled);
                     return true;
                 }
-                final InputMethodSettings settings = queryMethodMapForUser(userId);
+                final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
                 if (!settings.getMethodMap().containsKey(imeId)) {
                     return false; // IME is not found.
                 }
@@ -6550,7 +6582,7 @@
                 previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled);
             }
         } else {
-            final InputMethodSettings settings = queryMethodMapForUser(userId);
+            final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
             if (enabled) {
                 if (!settings.getMethodMap().containsKey(imeId)) {
                     failedToEnableUnknownIme = true;
@@ -6685,7 +6717,7 @@
                         nextEnabledImes = mSettings.getEnabledInputMethodList();
                     } else {
                         final AdditionalSubtypeMap additionalSubtypeMap =
-                                AdditionalSubtypeUtils.load(userId);
+                                AdditionalSubtypeMapRepository.get(userId);
                         final InputMethodSettings settings = queryInputMethodServicesInternal(
                                 mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO);
 
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 29ea071..c80f988 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -814,7 +814,8 @@
         // storage is locked, instead of when the user is stopped.  This would ensure the flags get
         // reset if CE storage is locked later for a user that allows delayed locking.
         if (android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             UserProperties userProperties = mUserManager.getUserProperties(UserHandle.of(userId));
             if (userProperties != null && userProperties.getAllowStoppingUserWithDelayedLocking()) {
                 return;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ba5882c..b98424c 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1204,6 +1204,10 @@
         }
     }
 
+    private static boolean privateSpaceFlagsEnabled() {
+        return allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures();
+    }
+
     private final class SavePolicyFileRunnable implements Runnable {
         @Override
         public void run() {
@@ -2142,7 +2146,7 @@
         }
 
         private boolean isProfileUnavailable(String action) {
-            return allowPrivateProfile() ?
+            return privateSpaceFlagsEnabled() ?
                     action.equals(Intent.ACTION_PROFILE_UNAVAILABLE) :
                     action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
         }
@@ -2744,7 +2748,7 @@
         filter.addAction(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
-        if (allowPrivateProfile()){
+        if (privateSpaceFlagsEnabled()){
             filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE);
         }
         getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 23d48e8..9af2b3f 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -389,7 +389,8 @@
      */
     boolean canLauncherAccessProfile(ComponentName launcherComponent, int userId) {
         if (android.os.Flags.allowPrivateProfile()
-                && Flags.enablePermissionToAccessHiddenProfiles()) {
+                && Flags.enablePermissionToAccessHiddenProfiles()
+                && Flags.enablePrivateSpaceFeatures()) {
             if (mUmInternal.getUserProperties(userId).getProfileApiVisibility()
                     != UserProperties.PROFILE_API_VISIBILITY_HIDDEN) {
                 return true;
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 3abf3a5..ecfc768 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -50,13 +50,17 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ApexStagedEvent;
 import android.content.pm.Flags;
+import android.content.pm.IPackageManagerNative;
+import android.content.pm.IStagedApexObserver;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.dex.ArtManager;
 import android.os.Binder;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
@@ -1054,6 +1058,10 @@
                 artManager.scheduleBackgroundDexoptJob();
             }
         }, new IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED));
+
+        if (Flags.useArtServiceV2()) {
+            StagedApexObserver.registerForStagedApexUpdates(artManager);
+        }
     }
 
     /**
@@ -1168,4 +1176,32 @@
                 && dexoptOptions.isCompilationEnabled()
                 && !isApex;
     }
+
+    private static class StagedApexObserver extends IStagedApexObserver.Stub {
+        private final @NonNull ArtManagerLocal mArtManager;
+
+        static void registerForStagedApexUpdates(@NonNull ArtManagerLocal artManager) {
+            IPackageManagerNative packageNative = IPackageManagerNative.Stub.asInterface(
+                    ServiceManager.getService("package_native"));
+            if (packageNative == null) {
+                Log.e(TAG, "No IPackageManagerNative");
+                return;
+            }
+
+            try {
+                packageNative.registerStagedApexObserver(new StagedApexObserver(artManager));
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to register staged apex observer", e);
+            }
+        }
+
+        private StagedApexObserver(@NonNull ArtManagerLocal artManager) {
+            mArtManager = artManager;
+        }
+
+        @Override
+        public void onApexStaged(@NonNull ApexStagedEvent event) {
+            mArtManager.onApexStaged(event.stagedApexModuleNames);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 6b56b85..c7ebb3c 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -584,7 +584,8 @@
             return android.os.Flags.allowPrivateProfile()
                     && Flags.enableHidingProfiles()
                     && Flags.enableLauncherAppsHiddenProfileChecks()
-                    && Flags.enablePermissionToAccessHiddenProfiles();
+                    && Flags.enablePermissionToAccessHiddenProfiles()
+                    && Flags.enablePrivateSpaceFeatures();
         }
 
         @VisibleForTesting // We override it in unit tests
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index fe8030b..7c51707 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -38,7 +38,6 @@
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
 import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
-import static android.util.FeatureFlagUtils.SETTINGS_TREAT_PAUSE_AS_QUARANTINE;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_INIT_TIME;
@@ -168,7 +167,6 @@
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.ExceptionUtils;
-import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -3208,15 +3206,8 @@
         }
 
         if (quarantined) {
-            final boolean hasQuarantineAppsPerm = mContext.checkCallingOrSelfPermission(
-                    android.Manifest.permission.QUARANTINE_APPS) == PERMISSION_GRANTED;
-            // TODO: b/305256093 - In order to facilitate testing, temporarily allowing apps
-            // with SUSPEND_APPS permission to quarantine apps. Remove this once the testing
-            // is done and this is no longer needed.
-            if (!hasQuarantineAppsPerm) {
-                mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS,
-                        callingMethod);
-            }
+            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.QUARANTINE_APPS,
+                    callingMethod);
         } else {
             mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS,
                     callingMethod);
@@ -6287,16 +6278,8 @@
                 SuspendDialogInfo dialogInfo, int flags, String suspendingPackage,
                 int suspendingUserId, int targetUserId) {
             final int callingUid = Binder.getCallingUid();
-            boolean quarantined = false;
-            if (Flags.quarantinedEnabled()) {
-                if ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0) {
-                    quarantined = true;
-                } else if (FeatureFlagUtils.isEnabled(mContext,
-                        SETTINGS_TREAT_PAUSE_AS_QUARANTINE)) {
-                    final String wellbeingPkg = mContext.getString(R.string.config_systemWellbeing);
-                    quarantined = suspendingPackage.equals(wellbeingPkg);
-                }
-            }
+            final boolean quarantined = ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0)
+                    && Flags.quarantinedEnabled();
             final Computer snapshot = snapshotComputer();
             final UserPackage suspender = UserPackage.of(targetUserId, suspendingPackage);
             enforceCanSetPackagesSuspendedAsUser(snapshot, quarantined, suspender, callingUid,
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 211b754..4c653f6 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2833,7 +2833,8 @@
 
     @VisibleForTesting
     boolean areShortcutsSupportedOnHomeScreen(@UserIdInt int userId) {
-        if (!android.os.Flags.allowPrivateProfile() || !Flags.disablePrivateSpaceItemsOnHome()) {
+        if (!android.os.Flags.allowPrivateProfile() || !Flags.disablePrivateSpaceItemsOnHome()
+                || !android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             return true;
         }
         final long start = getStatStartTime();
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7349755..88e7596 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -21,10 +21,15 @@
 import static android.content.Intent.EXTRA_USER_ID;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
+import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
 import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
 import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
+import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
 
 import static com.android.internal.app.SetScreenLockDialogActivity.EXTRA_ORIGIN_USER_ID;
 import static com.android.internal.app.SetScreenLockDialogActivity.LAUNCH_REASON_DISABLE_QUIET_MODE;
@@ -1006,9 +1011,17 @@
         emulateSystemUserModeIfNeeded();
     }
 
+    private boolean doesDeviceHardwareSupportPrivateSpace() {
+        return !mPm.hasSystemFeature(FEATURE_EMBEDDED, 0)
+                && !mPm.hasSystemFeature(FEATURE_WATCH, 0)
+                && !mPm.hasSystemFeature(FEATURE_LEANBACK, 0)
+                && !mPm.hasSystemFeature(FEATURE_AUTOMOTIVE, 0);
+    }
+
     private static boolean isAutoLockForPrivateSpaceEnabled() {
         return android.os.Flags.allowPrivateProfile()
-                && Flags.supportAutolockForPrivateSpace();
+                && Flags.supportAutolockForPrivateSpace()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures();
     }
 
     void systemReady() {
@@ -1052,7 +1065,8 @@
 
     private boolean isAutoLockingPrivateSpaceOnRestartsEnabled() {
         return android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.enablePrivateSpaceAutolockOnRestarts();
+                && android.multiuser.Flags.enablePrivateSpaceAutolockOnRestarts()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures();
     }
 
     /**
@@ -1493,7 +1507,8 @@
     private boolean isProfileHidden(int userId) {
         UserProperties userProperties = getUserPropertiesCopy(userId);
         if (android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.enableHidingProfiles()) {
+                && android.multiuser.Flags.enableHidingProfiles()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             return userProperties.getProfileApiVisibility()
                     == UserProperties.PROFILE_API_VISIBILITY_HIDDEN;
         }
@@ -1693,7 +1708,8 @@
                 setQuietModeEnabled(userId, true /* enableQuietMode */, target, callingPackage);
                 return true;
             }
-            if (android.os.Flags.allowPrivateProfile()) {
+            if (android.os.Flags.allowPrivateProfile()
+                    && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
                 final UserProperties userProperties = getUserPropertiesInternal(userId);
                 if (userProperties != null
                         && userProperties.isAuthAlwaysRequiredToDisableQuietMode()) {
@@ -1839,7 +1855,8 @@
         logQuietModeEnabled(userId, enableQuietMode, callingPackage);
 
         // Broadcast generic intents for all profiles
-        if (android.os.Flags.allowPrivateProfile()) {
+        if (android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             broadcastProfileAvailabilityChanges(profile, parent.getUserHandle(),
                     enableQuietMode, false);
         }
@@ -1852,7 +1869,8 @@
 
     private void stopUserForQuietMode(int userId) throws RemoteException {
         if (android.os.Flags.allowPrivateProfile()
-                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
+                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
             // Allow delayed locking since some profile types want to be able to unlock again via
             // biometrics.
             ActivityManager.getService()
@@ -2751,6 +2769,18 @@
     }
 
     @Override
+    public boolean canAddPrivateProfile(@UserIdInt int userId) {
+        checkCreateUsersPermission("canHaveRestrictedProfile");
+        UserInfo parentUserInfo = getUserInfo(userId);
+        return isUserTypeEnabled(USER_TYPE_PROFILE_PRIVATE)
+                && canAddMoreProfilesToUser(USER_TYPE_PROFILE_PRIVATE,
+                    userId, /* allowedToRemoveOne */ false)
+                && (parentUserInfo != null && parentUserInfo.isMain())
+                && doesDeviceHardwareSupportPrivateSpace()
+                && !hasUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, userId);
+    }
+
+    @Override
     public boolean hasRestrictedProfiles(@UserIdInt int userId) {
         checkManageUsersPermission("hasRestrictedProfiles");
         synchronized (mUsersLock) {
@@ -5308,7 +5338,7 @@
         if (!isUserTypeEnabled(userTypeDetails)) {
             throwCheckedUserOperationException(
                     "Cannot add a user of disabled type " + userType + ".",
-                    UserManager.USER_OPERATION_ERROR_MAX_USERS);
+                    UserManager.USER_OPERATION_ERROR_DISABLED_USER);
         }
 
         synchronized (mUsersLock) {
@@ -5341,6 +5371,7 @@
         final boolean isDemo = UserManager.isUserTypeDemo(userType);
         final boolean isManagedProfile = UserManager.isUserTypeManagedProfile(userType);
         final boolean isCommunalProfile = UserManager.isUserTypeCommunalProfile(userType);
+        final boolean isPrivateProfile = UserManager.isUserTypePrivateProfile(userType);
 
         final long ident = Binder.clearCallingIdentity();
         UserInfo userInfo;
@@ -5387,6 +5418,12 @@
                                     + " for user " + parentId,
                             UserManager.USER_OPERATION_ERROR_MAX_USERS);
                 }
+                if (android.multiuser.Flags.blockPrivateSpaceCreation()
+                        && isPrivateProfile && !canAddPrivateProfile(parentId)) {
+                    throwCheckedUserOperationException(
+                            "Cannot add profile of type " + userType + " for user " + parentId,
+                            UserManager.USER_OPERATION_ERROR_PRIVATE_PROFILE);
+                }
                 if (isRestricted && (parentId != UserHandle.USER_SYSTEM)
                         && !isCreationOverrideEnabled()) {
                     throwCheckedUserOperationException(
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 114daaa..7f9c1cf 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -292,6 +292,7 @@
                 .setName(USER_TYPE_PROFILE_PRIVATE)
                 .setBaseType(FLAG_PROFILE)
                 .setMaxAllowedPerParent(1)
+                .setEnabled(UserManager.isPrivateProfileEnabled() ? 1 : 0)
                 .setLabels(R.string.profile_label_private)
                 .setIconBadge(com.android.internal.R.drawable.ic_private_profile_icon_badge)
                 .setBadgePlain(com.android.internal.R.drawable.ic_private_profile_badge)
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index b490f57..6fc9d9a 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -208,6 +208,7 @@
      * potentially expensive or resource-linked objects, such as {@link IBinder}.
      */
     static final class DebugInfo {
+        final Status mStatus;
         final long mCreateTime;
         final CallerInfo mCallerInfo;
         @Nullable
@@ -220,7 +221,6 @@
         private final CombinedVibration mOriginalEffect;
         private final int mScaleLevel;
         private final float mAdaptiveScale;
-        private final Status mStatus;
 
         DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration playedEffect,
                 @Nullable CombinedVibration originalEffect, int scaleLevel,
@@ -253,6 +253,10 @@
                     + ", callerInfo: " + mCallerInfo;
         }
 
+        void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
+            statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
+        }
+
         /**
          * Write this info in a compact way into given {@link PrintWriter}.
          *
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index f510b4e..f3e226e 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -40,8 +40,10 @@
 import java.util.List;
 import java.util.PriorityQueue;
 import java.util.Queue;
+import java.util.concurrent.CancellationException;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Creates and manages a queue of steps for performing a VibrationEffect, as well as coordinating
@@ -70,6 +72,7 @@
 
     private final DeviceAdapter mDeviceAdapter;
     private final VibrationScaler mVibrationScaler;
+    private final VibratorFrameworkStatsLogger mStatsLogger;
 
     // Not guarded by lock because it's mostly used to read immutable fields by this conductor.
     // This is only modified here at the prepareToStart method which always runs at the vibration
@@ -103,14 +106,15 @@
     private int mSuccessfulVibratorOnSteps;
 
     VibrationStepConductor(HalVibration vib, VibrationSettings vibrationSettings,
-            DeviceAdapter deviceAdapter,
-            VibrationScaler vibrationScaler,
+            DeviceAdapter deviceAdapter, VibrationScaler vibrationScaler,
+            VibratorFrameworkStatsLogger statsLogger,
             CompletableFuture<Void> requestVibrationParamsFuture,
             VibrationThread.VibratorManagerHooks vibratorManagerHooks) {
         this.mVibration = vib;
         this.vibrationSettings = vibrationSettings;
         this.mDeviceAdapter = deviceAdapter;
         mVibrationScaler = vibrationScaler;
+        mStatsLogger = statsLogger;
         mRequestVibrationParamsFuture = requestVibrationParamsFuture;
         this.vibratorManagerHooks = vibratorManagerHooks;
         this.mSignalVibratorsComplete =
@@ -461,9 +465,19 @@
         }
 
         try {
-            mRequestVibrationParamsFuture.orTimeout(
+            mRequestVibrationParamsFuture.get(
                     vibrationSettings.getRequestVibrationParamsTimeoutMs(),
-                    TimeUnit.MILLISECONDS).get();
+                    TimeUnit.MILLISECONDS);
+        } catch (TimeoutException e) {
+            if (DEBUG) {
+                Slog.d(TAG, "Request for vibration params timed out", e);
+            }
+            mStatsLogger.logVibrationParamRequestTimeout(mVibration.callerInfo.uid);
+        } catch (CancellationException e) {
+            if (DEBUG) {
+                Slog.d(TAG, "Request for vibration params cancelled, maybe superseded or"
+                        + " vibrator controller unregistered. Skipping params...", e);
+            }
         } catch (Throwable e) {
             Slog.w(TAG, "Failed to retrieve vibration params.", e);
         }
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index ec3d99b..10317c9 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -72,19 +72,21 @@
     private final VibrationParamsRecords mVibrationParamsRecords;
     private final VibratorControllerHolder mVibratorControllerHolder;
     private final VibrationScaler mVibrationScaler;
+    private final VibratorFrameworkStatsLogger mStatsLogger;
     private final Object mLock;
     private final int[] mRequestVibrationParamsForUsages;
 
     @GuardedBy("mLock")
-    private CompletableFuture<Void> mRequestVibrationParamsFuture = null;
-    @GuardedBy("mLock")
-    private IBinder mRequestVibrationParamsToken;
+    @Nullable
+    private VibrationParamRequest mVibrationParamRequest = null;
 
     VibratorControlService(Context context,
             VibratorControllerHolder vibratorControllerHolder, VibrationScaler vibrationScaler,
-            VibrationSettings vibrationSettings, Object lock) {
+            VibrationSettings vibrationSettings, VibratorFrameworkStatsLogger statsLogger,
+            Object lock) {
         mVibratorControllerHolder = vibratorControllerHolder;
         mVibrationScaler = vibrationScaler;
+        mStatsLogger = statsLogger;
         mLock = lock;
         mRequestVibrationParamsForUsages = vibrationSettings.getRequestVibrationParamsForUsages();
 
@@ -180,20 +182,25 @@
         Objects.requireNonNull(requestToken);
 
         synchronized (mLock) {
-            if (mRequestVibrationParamsToken == null) {
+            if (mVibrationParamRequest == null) {
                 Slog.wtf(TAG,
                         "New vibration params received but no token was cached in the service. "
                                 + "New vibration params ignored.");
+                mStatsLogger.logVibrationParamResponseIgnored();
                 return;
             }
 
-            if (!Objects.equals(requestToken, mRequestVibrationParamsToken)) {
+            if (!Objects.equals(requestToken, mVibrationParamRequest.token)) {
                 Slog.w(TAG,
                         "New vibration params received but the provided token does not match the "
                                 + "cached one. New vibration params ignored.");
+                mStatsLogger.logVibrationParamResponseIgnored();
                 return;
             }
 
+            long latencyMs = SystemClock.uptimeMillis() - mVibrationParamRequest.uptimeMs;
+            mStatsLogger.logVibrationParamRequestLatency(mVibrationParamRequest.uid, latencyMs);
+
             updateAdaptiveHapticsScales(result);
             endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ false);
             recordUpdateVibrationParams(result, /* fromRequest= */ true);
@@ -222,7 +229,7 @@
      */
     @Nullable
     public CompletableFuture<Void> triggerVibrationParamsRequest(
-            @VibrationAttributes.Usage int usage, int timeoutInMillis) {
+            int uid, @VibrationAttributes.Usage int usage, int timeoutInMillis) {
         synchronized (mLock) {
             IVibratorController vibratorController =
                     mVibratorControllerHolder.getVibratorController();
@@ -241,16 +248,16 @@
 
             try {
                 endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
-                mRequestVibrationParamsFuture = new CompletableFuture<>();
-                mRequestVibrationParamsToken = new Binder();
+                mVibrationParamRequest = new VibrationParamRequest(uid);
                 vibratorController.requestVibrationParams(vibrationType, timeoutInMillis,
-                        mRequestVibrationParamsToken);
+                        mVibrationParamRequest.token);
+                return mVibrationParamRequest.future;
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to request vibration params.", e);
                 endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
             }
 
-            return mRequestVibrationParamsFuture;
+            return null;
         }
     }
 
@@ -276,13 +283,13 @@
     }
 
     /**
-     * Returns the {@link #mRequestVibrationParamsToken} which is used to validate
+     * Returns the binder token which is used to validate
      * {@link #onRequestVibrationParamsComplete(IBinder, VibrationParam[])} calls.
      */
     @VisibleForTesting
     public IBinder getRequestVibrationParamsToken() {
         synchronized (mLock) {
-            return mRequestVibrationParamsToken;
+            return mVibrationParamRequest == null ? null : mVibrationParamRequest.token;
         }
     }
 
@@ -293,7 +300,7 @@
         synchronized (mLock) {
             isVibratorControllerRegistered =
                     mVibratorControllerHolder.getVibratorController() != null;
-            hasPendingVibrationParamsRequest = mRequestVibrationParamsFuture != null;
+            hasPendingVibrationParamsRequest = mVibrationParamRequest != null;
         }
 
         pw.println("VibratorControlService:");
@@ -329,18 +336,10 @@
      */
     @GuardedBy("mLock")
     private void endOngoingRequestVibrationParamsLocked(boolean wasCancelled) {
-        mRequestVibrationParamsToken = null;
-        if (mRequestVibrationParamsFuture == null) {
-            return;
+        if (mVibrationParamRequest != null) {
+            mVibrationParamRequest.endRequest(wasCancelled);
         }
-
-        if (wasCancelled) {
-            mRequestVibrationParamsFuture.cancel(/* mayInterruptIfRunning= */ true);
-        } else {
-            mRequestVibrationParamsFuture.complete(null);
-        }
-
-        mRequestVibrationParamsFuture = null;
+        mVibrationParamRequest = null;
     }
 
     private static int mapToAdaptiveVibrationType(@VibrationAttributes.Usage int usage) {
@@ -423,6 +422,7 @@
      * @param scale The scaling factor that should be applied to the vibrations.
      */
     private void updateAdaptiveHapticsScales(int types, float scale) {
+        mStatsLogger.logVibrationParamScale(scale);
         for (int usage : mapFromAdaptiveVibrationTypeToVibrationUsages(types)) {
             updateOrRemoveAdaptiveHapticsScale(usage, scale);
         }
@@ -504,6 +504,27 @@
         }
     }
 
+    /** Represents a request for {@link VibrationParam}. */
+    private static final class VibrationParamRequest {
+        public final CompletableFuture<Void> future = new CompletableFuture<>();
+        public final IBinder token = new Binder();
+        public final int uid;
+        public final long uptimeMs;
+
+        VibrationParamRequest(int uid) {
+            this.uid = uid;
+            uptimeMs = SystemClock.uptimeMillis();
+        }
+
+        public void endRequest(boolean wasCancelled) {
+            if (wasCancelled) {
+                future.cancel(/* mayInterruptIfRunning= */ true);
+            } else {
+                future.complete(null);
+            }
+        }
+    }
+
     /**
      * Record for a single {@link Vibration.DebugInfo}, that can be grouped by usage and aggregated
      * by UID, {@link VibrationAttributes} and {@link VibrationEffect}.
diff --git a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
index 7e601b6..e9c3894 100644
--- a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
@@ -25,6 +25,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.modules.expresslog.Counter;
+import com.android.modules.expresslog.Histogram;
 
 import java.util.ArrayDeque;
 import java.util.Queue;
@@ -40,6 +41,23 @@
     // Warning about dropping entries after this amount of atoms were dropped by the throttle.
     private static final int VIBRATION_REPORTED_WARNING_QUEUE_SIZE = 200;
 
+    // Latency between 0ms and 99ms, with 100 representing overflow latencies >= 100ms.
+    // Underflow not expected.
+    private static final Histogram sVibrationParamRequestLatencyHistogram = new Histogram(
+            "vibrator.value_vibration_param_request_latency",
+            new Histogram.UniformOptions(20, 0, 100));
+
+    // Scales in [0, 2), with 2 representing overflow scales >= 2.
+    // Underflow expected to represent how many times scales were cleared (set to -1).
+    private static final Histogram sVibrationParamScaleHistogram = new Histogram(
+            "vibrator.value_vibration_param_scale", new Histogram.UniformOptions(20, 0, 2));
+
+    // Scales in [0, 2), with 2 representing overflow scales >= 2.
+    // Underflow not expected.
+    private static final Histogram sAdaptiveHapticScaleHistogram = new Histogram(
+            "vibrator.value_vibration_adaptive_haptic_scale",
+            new Histogram.UniformOptions(20, 0, 2));
+
     private final Object mLock = new Object();
     private final Handler mHandler;
     private final long mVibrationReportedLogIntervalMillis;
@@ -140,6 +158,33 @@
         }
     }
 
+    /** Logs adaptive haptic scale value applied to a vibration, only if it's not 1.0. */
+    public void logVibrationAdaptiveHapticScale(int uid, float scale) {
+        if (Float.compare(scale, 1f) != 0) {
+            sAdaptiveHapticScaleHistogram.logSampleWithUid(uid, scale);
+        }
+    }
+
+    /** Logs a vibration param scale value received by the vibrator control service. */
+    public void logVibrationParamScale(float scale) {
+        sVibrationParamScaleHistogram.logSample(scale);
+    }
+
+    /** Logs the latency of a successful vibration params request completed before a vibration. */
+    public void logVibrationParamRequestLatency(int uid, long latencyMs) {
+        sVibrationParamRequestLatencyHistogram.logSampleWithUid(uid, (float) latencyMs);
+    }
+
+    /** Logs a vibration params request timed out before a vibration. */
+    public void logVibrationParamRequestTimeout(int uid) {
+        Counter.logIncrementWithUid("vibrator.value_vibration_param_request_timeout", uid);
+    }
+
+    /** Logs when a response received for a vibration params request is ignored by the service. */
+    public void logVibrationParamResponseIgnored() {
+        Counter.logIncrement("vibrator.value_vibration_param_response_ignored");
+    }
+
     /** Logs only if the haptics feedback effect is one of the KEYBOARD_ constants. */
     public static void logPerformHapticsFeedbackIfKeyboard(int uid, int hapticsFeedbackEffect) {
         boolean isKeyboard;
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index c1bf039..af494a3 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -212,12 +212,13 @@
         mContext = context;
         mInjector = injector;
         mHandler = injector.createHandler(Looper.myLooper());
+        mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);
 
         mVibrationSettings = new VibrationSettings(mContext, mHandler);
         mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
         mVibratorControlService = new VibratorControlService(mContext,
                 injector.createVibratorControllerHolder(), mVibrationScaler, mVibrationSettings,
-                mLock);
+                mFrameworkStatsLogger, mLock);
         mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler);
 
         VibrationCompleteListener listener = new VibrationCompleteListener(this);
@@ -235,7 +236,6 @@
                 recentDumpSizeLimit, dumpSizeLimit, dumpAggregationTimeLimit);
 
         mBatteryStatsService = injector.getBatteryStatsService();
-        mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);
 
         mAppOps = mContext.getSystemService(AppOpsManager.class);
 
@@ -853,9 +853,7 @@
     private void endVibrationLocked(HalVibration vib, Vibration.EndInfo vibrationEndInfo,
             boolean shouldWriteStats) {
         vib.end(vibrationEndInfo);
-        logVibrationStatus(vib.callerInfo.uid, vib.callerInfo.attrs,
-                vibrationEndInfo.status);
-        mVibratorManagerRecords.record(vib);
+        logAndRecordVibration(vib.getDebugInfo());
         if (shouldWriteStats) {
             mFrameworkStatsLogger.writeVibrationReportedAsync(
                     vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
@@ -866,9 +864,7 @@
     private void endVibrationAndWriteStatsLocked(ExternalVibrationHolder vib,
             Vibration.EndInfo vibrationEndInfo) {
         vib.end(vibrationEndInfo);
-        logVibrationStatus(vib.externalVibration.getUid(),
-                vib.externalVibration.getVibrationAttributes(), vibrationEndInfo.status);
-        mVibratorManagerRecords.record(vib);
+        logAndRecordVibration(vib.getDebugInfo());
         mFrameworkStatsLogger.writeVibrationReportedAsync(
                 vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
     }
@@ -882,12 +878,12 @@
                 vib.callerInfo.attrs.getUsage())) {
             requestVibrationParamsFuture =
                     mVibratorControlService.triggerVibrationParamsRequest(
-                            vib.callerInfo.attrs.getUsage(),
+                            vib.callerInfo.uid, vib.callerInfo.attrs.getUsage(),
                             mVibrationSettings.getRequestVibrationParamsTimeoutMs());
         }
 
         return new VibrationStepConductor(vib, mVibrationSettings, mDeviceAdapter, mVibrationScaler,
-                requestVibrationParamsFuture, mVibrationThreadCallbacks);
+                mFrameworkStatsLogger, requestVibrationParamsFuture, mVibrationThreadCallbacks);
     }
 
     private Vibration.EndInfo startVibrationOnInputDevicesLocked(HalVibration vib) {
@@ -903,6 +899,12 @@
         return new Vibration.EndInfo(Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
     }
 
+    private void logAndRecordVibration(Vibration.DebugInfo info) {
+        info.logMetrics(mFrameworkStatsLogger);
+        logVibrationStatus(info.mCallerInfo.uid, info.mCallerInfo.attrs, info.mStatus);
+        mVibratorManagerRecords.record(info);
+    }
+
     private void logVibrationStatus(int uid, VibrationAttributes attrs,
             Vibration.Status status) {
         switch (status) {
@@ -1752,15 +1754,7 @@
                     new VibrationRecords(recentVibrationSizeLimit, /* aggregationTimeLimit= */ 0);
         }
 
-        synchronized void record(HalVibration vib) {
-            record(vib.getDebugInfo());
-        }
-
-        synchronized void record(ExternalVibrationHolder vib) {
-            record(vib.getDebugInfo());
-        }
-
-        private synchronized void record(Vibration.DebugInfo info) {
+        synchronized void record(Vibration.DebugInfo info) {
             GroupedAggregatedLogRecords.AggregatedLogRecord<VibrationRecord> droppedRecord =
                     mRecentVibrations.add(new VibrationRecord(info));
             if (droppedRecord != null) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 73aa307..d87e21c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1933,8 +1933,8 @@
                 td.setEnsureStatusBarContrastWhenTransparent(
                         atd.getEnsureStatusBarContrastWhenTransparent());
             }
-            if (td.getStatusBarAppearance() == 0) {
-                td.setStatusBarAppearance(atd.getStatusBarAppearance());
+            if (td.getSystemBarsAppearance() == 0) {
+                td.setSystemBarsAppearance(atd.getSystemBarsAppearance());
             }
             if (td.getNavigationBarColor() == 0) {
                 td.setNavigationBarColor(atd.getNavigationBarColor());
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index f4d95af..b9c5b36 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -201,6 +201,7 @@
         when(mMockUserManagerInternal.isUserRunning(anyInt())).thenReturn(true);
         when(mMockUserManagerInternal.getProfileIds(anyInt(), anyBoolean()))
                 .thenReturn(new int[] {0});
+        when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[] {0});
         when(mMockActivityManagerInternal.isSystemReady()).thenReturn(true);
         when(mMockPackageManagerInternal.getPackageUid(anyString(), anyLong(), anyInt()))
                 .thenReturn(Binder.getCallingUid());
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 9c9aeea..705dac5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -19,6 +19,7 @@
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -72,7 +73,7 @@
     private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 4000;
     private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 1000;
     private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 2000;
-    private static final float DOZE_SCALE_FACTOR = 0.0f;
+    private static final float DOZE_SCALE_FACTOR = 0.54f;
     private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false;
     private static final int LIGHT_SENSOR_WARMUP_TIME = 0;
     private static final int AMBIENT_LIGHT_HORIZON_SHORT = 1000;
@@ -87,6 +88,7 @@
     @Mock SensorManager mSensorManager;
     @Mock BrightnessMappingStrategy mBrightnessMappingStrategy;
     @Mock BrightnessMappingStrategy mIdleBrightnessMappingStrategy;
+    @Mock BrightnessMappingStrategy mDozeBrightnessMappingStrategy;
     @Mock HysteresisLevels mAmbientBrightnessThresholds;
     @Mock HysteresisLevels mScreenBrightnessThresholds;
     @Mock HysteresisLevels mAmbientBrightnessThresholdsIdle;
@@ -124,12 +126,15 @@
 
         when(mBrightnessMappingStrategy.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_DEFAULT);
         when(mIdleBrightnessMappingStrategy.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_IDLE);
+        when(mDozeBrightnessMappingStrategy.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_DOZE);
 
         SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap = new SparseArray<>();
         brightnessMappingStrategyMap.append(AUTO_BRIGHTNESS_MODE_DEFAULT,
                 mBrightnessMappingStrategy);
         brightnessMappingStrategyMap.append(AUTO_BRIGHTNESS_MODE_IDLE,
                 mIdleBrightnessMappingStrategy);
+        brightnessMappingStrategyMap.append(AUTO_BRIGHTNESS_MODE_DOZE,
+                mDozeBrightnessMappingStrategy);
         mController = new AutomaticBrightnessController(
                 new AutomaticBrightnessController.Injector() {
                     @Override
@@ -1032,10 +1037,9 @@
         float normalizedBrightness = 0.3f;
         when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
         when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
-        when(mBrightnessMappingStrategy.getBrightness(eq(lux), eq(null), anyInt()))
-                .thenReturn(normalizedBrightness);
+        when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
+                /* category= */ anyInt())).thenReturn(normalizedBrightness);
         when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
-        when(mBrightnessThrottler.isThrottled()).thenReturn(true);
 
         // Send a new sensor value, disable the sensor and verify
         listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
@@ -1047,4 +1051,79 @@
                 mController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
                         /* brightnessEvent= */ null), EPSILON);
     }
+
+    @Test
+    public void testAutoBrightnessInDoze_ShouldScaleIfNotUsingDozeCurve() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Set up system to return 0.3f as a brightness value
+        float lux = 100.0f;
+        // Brightness as float (from 0.0f to 1.0f)
+        float normalizedBrightness = 0.3f;
+        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
+        when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
+                /* category= */ anyInt())).thenReturn(normalizedBrightness);
+        when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+
+        // Set policy to DOZE
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
+                /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChanged= */ false, DisplayPowerRequest.POLICY_DOZE,
+                /* shouldResetShortTermModel= */ true);
+
+        // Send a new sensor value
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
+
+        // The brightness should be scaled by the doze factor
+        assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR,
+                mController.getAutomaticScreenBrightness(
+                        /* brightnessEvent= */ null), EPSILON);
+        assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR,
+                mController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+                        /* brightnessEvent= */ null), EPSILON);
+    }
+
+    @Test
+    public void testAutoBrightnessInDoze_ShouldNotScaleIfUsingDozeCurve() throws Exception {
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Set up system to return 0.3f as a brightness value
+        float lux = 100.0f;
+        // Brightness as float (from 0.0f to 1.0f)
+        float normalizedBrightness = 0.3f;
+        when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+        when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
+        when(mDozeBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
+                /* category= */ anyInt())).thenReturn(normalizedBrightness);
+        when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+
+        // Switch mode to DOZE
+        mController.switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+
+        // Set policy to DOZE
+        mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
+                /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChanged= */ false, DisplayPowerRequest.POLICY_DOZE,
+                /* shouldResetShortTermModel= */ true);
+
+        // Send a new sensor value
+        listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
+
+        // The brightness should not be scaled by the doze factor
+        assertEquals(normalizedBrightness,
+                mController.getAutomaticScreenBrightness(
+                        /* brightnessEvent= */ null), EPSILON);
+        assertEquals(normalizedBrightness,
+                mController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+                        /* brightnessEvent= */ null), EPSILON);
+    }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 14d8a9c..76b7780 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -114,6 +114,8 @@
     private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
     private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
     private static final float PROX_SENSOR_MAX_RANGE = 5;
+    private static final float DOZE_SCALE_FACTOR = 0.34f;
+
     private static final float BRIGHTNESS_RAMP_RATE_MINIMUM = 0.0f;
     private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
     private static final float BRIGHTNESS_RAMP_RATE_FAST_INCREASE = 0.4f;
@@ -193,6 +195,9 @@
 
         mContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.bool.config_displayColorFadeDisabled, false);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
+                DOZE_SCALE_FACTOR);
 
         doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
                 SystemProperties.set(anyString(), any()));
@@ -1705,7 +1710,7 @@
     }
 
     @Test
-    public void testInitialDozeBrightness() {
+    public void testInitialDozeBrightness_AutoBrightnessEnabled() {
         when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
         when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
         mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
@@ -1734,6 +1739,39 @@
     }
 
     @Test
+    public void testInitialDozeBrightness_AutoBrightnessDisabled() {
+        when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+        mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+        float brightness = 0.277f;
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        when(mHolder.hbmController.getCurrentBrightnessMax())
+                .thenReturn(PowerManager.BRIGHTNESS_MAX);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState, initialize
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(brightness * DOZE_SCALE_FACTOR),
+                /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
+                /* ignoreAnimationLimits= */ anyBoolean());
+        assertEquals(brightness * DOZE_SCALE_FACTOR, mHolder.dpc.getDozeBrightnessForOffload(),
+                /* delta= */ 0);
+    }
+
+    @Test
     public void testInitialDozeBrightness_AbcIsNull() {
         when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
         when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index 0e89d83..a3728c8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -18,8 +18,10 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.ContentResolver;
@@ -268,4 +270,33 @@
                 mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
                         Display.STATE_ON));
     }
+
+    @Test
+    public void selectStrategyCallsPostProcessorForAllStrategies() {
+        when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true);
+        mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
+                mInjector, DISPLAY_ID, mDisplayManagerFlags);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(0.3f);
+
+        mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest, Display.STATE_ON);
+
+        StrategySelectionNotifyRequest strategySelectionNotifyRequest =
+                new StrategySelectionNotifyRequest(mFollowerBrightnessStrategy);
+        verify(mInvalidBrightnessStrategy).strategySelectionPostProcessor(
+                eq(strategySelectionNotifyRequest));
+        verify(mScreenOffBrightnessModeStrategy).strategySelectionPostProcessor(
+                eq(strategySelectionNotifyRequest));
+        verify(mDozeBrightnessModeStrategy).strategySelectionPostProcessor(
+                eq(strategySelectionNotifyRequest));
+        verify(mFollowerBrightnessStrategy).strategySelectionPostProcessor(
+                eq(strategySelectionNotifyRequest));
+        verify(mBoostBrightnessStrategy).strategySelectionPostProcessor(
+                eq(strategySelectionNotifyRequest));
+        verify(mOverrideBrightnessStrategy).strategySelectionPostProcessor(
+                eq(strategySelectionNotifyRequest));
+        verify(mTemporaryBrightnessStrategy).strategySelectionPostProcessor(
+                eq(strategySelectionNotifyRequest));
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 7bbcd50..bc7c9a5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -15,6 +15,10 @@
  */
 package com.android.server.pm;
 
+import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
+import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.os.UserManager.DISALLOW_OUTGOING_CALLS;
 import static android.os.UserManager.DISALLOW_SMS;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
@@ -41,6 +45,7 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.KeyguardManager;
 import android.content.Context;
@@ -48,6 +53,7 @@
 import android.content.pm.UserInfo;
 import android.multiuser.Flags;
 import android.os.PowerManager;
+import android.os.ServiceSpecificException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -76,6 +82,7 @@
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -120,11 +127,14 @@
 
     private static final String TAG_RESTRICTIONS = "restrictions";
 
+    private static final String PRIVATE_PROFILE_NAME = "TestPrivateProfile";
+
     @Rule
     public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
             .spyStatic(UserManager.class)
             .spyStatic(LocalServices.class)
             .spyStatic(SystemProperties.class)
+            .spyStatic(ActivityManager.class)
             .mockStatic(Settings.Global.class)
             .mockStatic(Settings.Secure.class)
             .build();
@@ -163,6 +173,7 @@
     @Before
     @UiThreadTest // Needed to initialize main handler
     public void setFixtures() {
+        MockitoAnnotations.initMocks(this);
         mSpiedContext = spy(mRealContext);
 
         // Called when WatchedUserStates is constructed
@@ -172,11 +183,12 @@
         when(mDeviceStorageMonitorInternal.isMemoryLow()).thenReturn(false);
         mockGetLocalService(DeviceStorageMonitorInternal.class, mDeviceStorageMonitorInternal);
         when(mSpiedContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager);
-        when(mSpiedContext.getSystemService(KeyguardManager.class)).thenReturn(mKeyguardManager);
+        doReturn(mKeyguardManager).when(mSpiedContext).getSystemService(KeyguardManager.class);
         when(mSpiedContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
         mockGetLocalService(LockSettingsInternal.class, mLockSettingsInternal);
         mockGetLocalService(PackageManagerInternal.class, mPackageManagerInternal);
         doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
+        mockIsLowRamDevice(false);
 
         // Must construct UserManagerService in the UiThread
         mTestDir = new File(mRealContext.getDataDir(), "umstest");
@@ -570,9 +582,10 @@
 
     @Test
     public void testAutoLockPrivateProfile() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         UserManagerService mSpiedUms = spy(mUms);
         UserInfo privateProfileUser =
-                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
                         USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
         Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync(
                 eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), any(),
@@ -587,10 +600,11 @@
 
     @Test
     public void testAutoLockOnDeviceLockForPrivateProfile() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         UserManagerService mSpiedUms = spy(mUms);
         UserInfo privateProfileUser =
-                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
                 USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
         mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
         Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync(
@@ -606,10 +620,11 @@
 
     @Test
     public void testAutoLockOnDeviceLockForPrivateProfile_keyguardUnlocked() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         UserManagerService mSpiedUms = spy(mUms);
         UserInfo privateProfileUser =
-                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
                 USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
         mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
 
@@ -623,10 +638,11 @@
 
     @Test
     public void testAutoLockOnDeviceLockForPrivateProfile_flagDisabled() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         UserManagerService mSpiedUms = spy(mUms);
         UserInfo privateProfileUser =
-                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
                 USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
 
         mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true);
@@ -641,13 +657,14 @@
 
     @Test
     public void testAutoLockAfterInactityForPrivateProfile() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
         UserManagerService mSpiedUms = spy(mUms);
         mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
         when(mPowerManager.isInteractive()).thenReturn(false);
 
         UserInfo privateProfileUser =
-                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+                mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
                         USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
         Mockito.doNothing().when(mSpiedUms).scheduleMessageToAutoLockPrivateSpace(
                 eq(privateProfileUser.getUserHandle().getIdentifier()), any(),
@@ -662,6 +679,7 @@
 
     @Test
     public void testSetOrUpdateAutoLockPreference_noPrivateProfile() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
 
         mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
@@ -675,8 +693,9 @@
 
     @Test
     public void testSetOrUpdateAutoLockPreference() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
-        mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+        mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
                         USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
 
         // Set the preference to auto lock on device lock
@@ -733,6 +752,77 @@
         }
     }
 
+    @Test
+    public void testCreatePrivateProfileOnHeadlessSystemUser_shouldAllowCreation() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+        UserManagerService mSpiedUms = spy(mUms);
+        int mainUser = mSpiedUms.getMainUserId();
+        doReturn(true).when(mSpiedUms).isHeadlessSystemUserMode();
+        assertThat(mSpiedUms.canAddPrivateProfile(mainUser)).isTrue();
+        assertThat(mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(
+                PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null)).isNotNull();
+    }
+
+    @Test
+    public void testCreatePrivateProfileOnSecondaryUser_shouldNotAllowCreation() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+        UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0);
+        assertThat(mUms.canAddPrivateProfile(user.id)).isFalse();
+        assertThrows(ServiceSpecificException.class,
+                () -> mUms.createProfileForUserWithThrow(PRIVATE_PROFILE_NAME,
+                        USER_TYPE_PROFILE_PRIVATE, 0, user.id, null));
+    }
+
+    @Test
+    public void testCreatePrivateProfileOnAutoDevices_shouldNotAllowCreation() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+        doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_AUTOMOTIVE), anyInt());
+        int mainUser = mUms.getMainUserId();
+        assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+        assertThrows(ServiceSpecificException.class,
+                () -> mUms.createProfileForUserWithThrow(PRIVATE_PROFILE_NAME,
+                        USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+    }
+
+    @Test
+    public void testCreatePrivateProfileOnTV_shouldNotAllowCreation() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+        doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_LEANBACK), anyInt());
+        int mainUser = mUms.getMainUserId();
+        assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+        assertThrows(ServiceSpecificException.class,
+                () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
+                        USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+    }
+
+    @Test
+    public void testCreatePrivateProfileOnEmbedded_shouldNotAllowCreation() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+        doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_EMBEDDED), anyInt());
+        int mainUser = mUms.getMainUserId();
+        assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+        assertThrows(ServiceSpecificException.class,
+                () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
+                        USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+    }
+
+    @Test
+    public void testCreatePrivateProfileOnWatch_shouldNotAllowCreation() {
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+        doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_WATCH), anyInt());
+        int mainUser = mUms.getMainUserId();
+        assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+        assertThrows(ServiceSpecificException.class,
+                () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
+                        USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+    }
+
     /**
      * Returns true if the user's XML file has Default restrictions
      * @param userId Id of the user.
@@ -800,6 +890,10 @@
                 any(), eq(android.provider.Settings.Global.USER_SWITCHER_ENABLED), anyInt()));
     }
 
+    private void mockIsLowRamDevice(boolean isLowRamDevice) {
+        doReturn(isLowRamDevice).when(ActivityManager::isLowRamDeviceStatic);
+    }
+
     private void mockDeviceDemoMode(boolean enabled) {
         doReturn(enabled ? 1 : 0).when(() -> Settings.Global.getInt(
                 any(), eq(android.provider.Settings.Global.DEVICE_DEMO_MODE), anyInt()));
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
index 6ac56bf..4ac4484 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
@@ -1,5 +1,5 @@
 {
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "RollbackPackageHealthObserverTests",
       "options": [
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index cea10ea..ea1a68a 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -825,7 +825,8 @@
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
         assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* expectLocking= */ true);
         verifyUserUnassignedFromDisplay(TEST_USER_ID1);
@@ -842,7 +843,8 @@
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
         assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ false);
@@ -852,19 +854,28 @@
     public void testStopPrivateProfileWithDelayedLocking_flagDisabled() throws Exception {
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
-        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.disableFlags(
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
         assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ true);
 
-        mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         mSetFlagsRule.enableFlags(
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
         setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE);
         assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ true);
+
+        mSetFlagsRule.disableFlags(android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
+        mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+        setUpAndStartProfileInBackground(TEST_USER_ID3, UserManager.USER_TYPE_PROFILE_PRIVATE);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ true,
+                /* keyEvictedCallback */ null, /* expectLocking= */ true);
     }
 
     /** Delayed-locking users (as opposed to devices) have no limits on how many can be unlocked. */
@@ -874,7 +885,8 @@
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
                 /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false);
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
         setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED);
         assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
@@ -890,7 +902,8 @@
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
-                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+                android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
         assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ true);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 507b3fe..1591a96 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -316,6 +316,10 @@
                 .that(userTypeDetails).isNotNull();
         final UserProperties typeProps = userTypeDetails.getDefaultUserPropertiesReference();
 
+        // Only run the test if private profile creation is enabled on the device
+        assumeTrue("Private profile not enabled on the device",
+                mUserManager.canAddPrivateProfile());
+
         // Test that only one private profile  can be created
         final int mainUserId = mainUser.getIdentifier();
         UserInfo userInfo = createProfileForUser("Private profile1",
@@ -1231,6 +1235,20 @@
 
     @MediumTest
     @Test
+    public void testPrivateProfileCreationRestrictions() {
+        assumeTrue(mUserManager.canAddPrivateProfile());
+        final int mainUserId = ActivityManager.getCurrentUser();
+        try {
+            UserInfo privateProfileInfo = createProfileForUser("Private",
+                            UserManager.USER_TYPE_PROFILE_PRIVATE, mainUserId);
+            assertThat(privateProfileInfo).isNotNull();
+        } catch (Exception e) {
+            fail("Creation of private profile failed due to " + e.getMessage());
+        }
+    }
+
+    @MediumTest
+    @Test
     public void testAddRestrictedProfile() throws Exception {
         if (isAutomotive() || UserManager.isHeadlessSystemUserMode()) return;
         assertWithMessage("There should be no associated restricted profiles before the test")
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index e3ea55a..03f2749 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -14092,7 +14092,8 @@
 
     @Test
     public void testProfileUnavailableIntent() throws RemoteException {
-        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_UNAVAILABLE);
         verify(mWorkerHandler).post(any(Runnable.class));
         verify(mSnoozeHelper).clearData(anyInt());
@@ -14101,7 +14102,8 @@
 
     @Test
     public void testManagedProfileUnavailableIntent() throws RemoteException {
-        mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
         simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
         verify(mWorkerHandler).post(any(Runnable.class));
         verify(mSnoozeHelper).clearData(anyInt());
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 6e478d8..55fc03e 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -110,6 +110,7 @@
     @Mock private VibratorController.OnVibrationCompleteListener mControllerCallbacks;
     @Mock private IBinder mVibrationToken;
     @Mock private VibrationConfig mVibrationConfigMock;
+    @Mock private VibratorFrameworkStatsLogger mStatsLoggerMock;
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
     private VibrationSettings mVibrationSettings;
@@ -255,6 +256,7 @@
                 USAGE_RINGTONE);
         waitForCompletion();
 
+        verify(mStatsLoggerMock, never()).logVibrationParamRequestTimeout(UID);
         assertEquals(Arrays.asList(expectedOneShot(15)),
                 mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
         List<Float> amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes();
@@ -274,6 +276,7 @@
         long vibrationId = startThreadAndDispatcher(effect, neverCompletingFuture, USAGE_RINGTONE);
         waitForCompletion();
 
+        verify(mStatsLoggerMock).logVibrationParamRequestTimeout(UID);
         assertEquals(Arrays.asList(expectedOneShot(15)),
                 mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
         assertEquals(expectedAmplitudes(1, 1, 1),
@@ -1679,7 +1682,7 @@
         mControllers = createVibratorControllers();
         DeviceAdapter deviceAdapter = new DeviceAdapter(mVibrationSettings, mControllers);
         mVibrationConductor = new VibrationStepConductor(vib, mVibrationSettings, deviceAdapter,
-                mVibrationScaler, requestVibrationParamsFuture, mManagerHooks);
+                mVibrationScaler, mStatsLoggerMock, requestVibrationParamsFuture, mManagerHooks);
         assertTrue(mThread.runVibrationOnVibrationThread(mVibrationConductor));
         return mVibrationConductor.getVibration().id;
     }
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 3799abc..3f5217c 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -27,6 +27,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
@@ -38,6 +42,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.test.TestLooper;
 import android.util.SparseArray;
 
@@ -59,13 +64,14 @@
 
 public class VibratorControlServiceTest {
 
+    private static final int UID = Process.ROOT_UID;
+
     @Rule
     public MockitoRule rule = MockitoJUnit.rule();
 
-    @Mock
-    private VibrationScaler mMockVibrationScaler;
-    @Mock
-    private PackageManagerInternal mPackageManagerInternalMock;
+    @Mock private VibrationScaler mMockVibrationScaler;
+    @Mock private PackageManagerInternal mPackageManagerInternalMock;
+    @Mock private VibratorFrameworkStatsLogger mStatsLoggerMock;
 
     private TestLooper mTestLooper;
     private FakeVibratorController mFakeVibratorController;
@@ -88,7 +94,7 @@
         mFakeVibratorController = new FakeVibratorController(mTestLooper.getLooper());
         mVibratorControlService = new VibratorControlService(
                 InstrumentationRegistry.getContext(), new VibratorControllerHolder(),
-                mMockVibrationScaler, mVibrationSettings, mLock);
+                mMockVibrationScaler, mVibrationSettings, mStatsLoggerMock, mLock);
     }
 
     @Test
@@ -123,7 +129,7 @@
         mVibratorControlService.registerVibratorController(mFakeVibratorController);
         int timeoutInMillis = 10;
         CompletableFuture<Void> future =
-                mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+                mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
                         timeoutInMillis);
         IBinder token = mVibratorControlService.getRequestVibrationParamsToken();
 
@@ -134,6 +140,11 @@
         mVibratorControlService.onRequestVibrationParamsComplete(token,
                 VibrationParamGenerator.generateVibrationParams(vibrationScales));
 
+        verify(mStatsLoggerMock).logVibrationParamRequestLatency(eq(UID), anyLong());
+        verify(mStatsLoggerMock).logVibrationParamScale(0.7f);
+        verify(mStatsLoggerMock).logVibrationParamScale(0.4f);
+        verifyNoMoreInteractions(mStatsLoggerMock);
+
         verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_ALARM, 0.7f);
         verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.4f);
         // Setting ScaleParam.TYPE_NOTIFICATION will update vibration scaling for both
@@ -150,7 +161,7 @@
         mVibratorControlService.registerVibratorController(mFakeVibratorController);
         int timeoutInMillis = 10;
         CompletableFuture<Void> unusedFuture =
-                mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+                mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
                         timeoutInMillis);
 
         SparseArray<Float> vibrationScales = new SparseArray<>();
@@ -160,6 +171,9 @@
         mVibratorControlService.onRequestVibrationParamsComplete(new Binder(),
                 VibrationParamGenerator.generateVibrationParams(vibrationScales));
 
+        verify(mStatsLoggerMock).logVibrationParamResponseIgnored();
+        verifyNoMoreInteractions(mStatsLoggerMock);
+
         verifyZeroInteractions(mMockVibrationScaler);
     }
 
@@ -174,6 +188,10 @@
                 VibrationParamGenerator.generateVibrationParams(vibrationScales),
                 mFakeVibratorController);
 
+        verify(mStatsLoggerMock).logVibrationParamScale(0.7f);
+        verify(mStatsLoggerMock).logVibrationParamScale(0.4f);
+        verifyNoMoreInteractions(mStatsLoggerMock);
+
         verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_ALARM, 0.7f);
         verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.4f);
         // Setting ScaleParam.TYPE_NOTIFICATION will update vibration scaling for both
@@ -192,6 +210,7 @@
                 VibrationParamGenerator.generateVibrationParams(vibrationScales),
                 mFakeVibratorController);
 
+        verify(mStatsLoggerMock, never()).logVibrationParamScale(anyFloat());
         verifyZeroInteractions(mMockVibrationScaler);
     }
 
@@ -202,6 +221,8 @@
 
         mVibratorControlService.clearVibrationParams(types, mFakeVibratorController);
 
+        verify(mStatsLoggerMock).logVibrationParamScale(-1f);
+
         verify(mMockVibrationScaler).removeAdaptiveHapticsScale(USAGE_ALARM);
         verify(mMockVibrationScaler).removeAdaptiveHapticsScale(USAGE_NOTIFICATION);
         // Clearing ScaleParam.TYPE_NOTIFICATION will clear vibration scaling for both
@@ -214,6 +235,7 @@
         mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM,
                 mFakeVibratorController);
 
+        verify(mStatsLoggerMock, never()).logVibrationParamScale(anyFloat());
         verifyZeroInteractions(mMockVibrationScaler);
     }
 
@@ -222,7 +244,7 @@
         int timeoutInMillis = 10;
         mVibratorControlService.registerVibratorController(mFakeVibratorController);
         CompletableFuture<Void> future =
-                mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+                mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
                         timeoutInMillis);
         try {
             future.orTimeout(timeoutInMillis, TimeUnit.MILLISECONDS).get();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 1ea90f5..d6ac517 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -70,7 +70,6 @@
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.VibrationAttributes;
@@ -1551,6 +1550,48 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+    public void vibrate_withAdaptiveHaptics_appliesCorrectAdaptiveScales() throws Exception {
+        // Keep user settings the same as device default so only adaptive scale is applied.
+        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
+                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_ALARM));
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_NOTIFICATION));
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_TOUCH));
+
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
+        VibratorManagerService service = createSystemReadyService();
+
+        SparseArray<Float> vibrationScales = new SparseArray<>();
+        vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+        vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+        mVibratorControlService.setVibrationParams(
+                VibrationParamGenerator.generateVibrationParams(vibrationScales),
+                mFakeVibratorController);
+
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                .compose();
+        vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
+        vibrateAndWaitUntilFinished(service, effect, NOTIFICATION_ATTRS);
+        vibrateAndWaitUntilFinished(service, effect, HAPTIC_FEEDBACK_ATTRS);
+
+        List<VibrationEffectSegment> segments = fakeVibrator.getAllEffectSegments();
+        assertEquals(3, segments.size());
+        assertEquals(0.7f, ((PrimitiveSegment) segments.get(0)).getScale(), 1e-5);
+        assertEquals(0.4f, ((PrimitiveSegment) segments.get(1)).getScale(), 1e-5);
+        assertEquals(1f, ((PrimitiveSegment) segments.get(2)).getScale(), 1e-5);
+        verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 0.7f);
+        verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 0.4f);
+        verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 1f);
+    }
+
+    @Test
     public void vibrate_withPowerModeChange_cancelVibrationIfNotAllowed() throws Exception {
         mockVibrators(1, 2);
         VibratorManagerService service = createSystemReadyService();
@@ -1998,8 +2039,7 @@
 
     @Test
     @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
-    public void onExternalVibration_withAdaptiveHaptics_returnsCorrectAdaptiveScales()
-            throws RemoteException {
+    public void onExternalVibration_withAdaptiveHaptics_returnsCorrectAdaptiveScales() {
         mockVibrators(1);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
                 IVibrator.CAP_AMPLITUDE_CONTROL);
@@ -2020,6 +2060,7 @@
         mExternalVibratorService.onExternalVibrationStop(externalVibration);
 
         assertEquals(scale.adaptiveHapticsScale, 0.7f, 0);
+        verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 0.7f);
 
         externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
                 AUDIO_NOTIFICATION_ATTRS,
@@ -2028,6 +2069,7 @@
         mExternalVibratorService.onExternalVibrationStop(externalVibration);
 
         assertEquals(scale.adaptiveHapticsScale, 0.4f, 0);
+        verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 0.4f);
 
         AudioAttributes ringtoneAudioAttrs = new AudioAttributes.Builder()
                 .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
@@ -2036,14 +2078,15 @@
                 ringtoneAudioAttrs,
                 mock(IExternalVibrationController.class));
         scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        mExternalVibratorService.onExternalVibrationStop(externalVibration);
 
         assertEquals(scale.adaptiveHapticsScale, 1f, 0);
+        verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 1f);
     }
 
     @Test
     @RequiresFlagsDisabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
-    public void onExternalVibration_withAdaptiveHapticsFlagDisabled_alwaysReturnScaleNone()
-            throws RemoteException {
+    public void onExternalVibration_withAdaptiveHapticsFlagDisabled_alwaysReturnScaleNone() {
         mockVibrators(1);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
                 IVibrator.CAP_AMPLITUDE_CONTROL);
@@ -2072,8 +2115,10 @@
                 AUDIO_NOTIFICATION_ATTRS,
                 mock(IExternalVibrationController.class));
         scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        mExternalVibratorService.onExternalVibrationStop(externalVibration);
 
         assertEquals(scale.adaptiveHapticsScale, 1f, 0);
+        verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 1f);
     }
 
     @Test