Merge "Add automotive_code_coverage keyword to Car host unit tests" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index dd919ca..a0f38d9 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -680,6 +680,11 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+cc_aconfig_library {
+    name: "com.android.media.flags.editing-aconfig-cc",
+    aconfig_declarations: "com.android.media.flags.editing-aconfig",
+}
+
 // MediaProjection
 aconfig_declarations {
     name: "com.android.media.flags.projection-aconfig",
diff --git a/Android.bp b/Android.bp
index 5b9f2cb..8c01585 100644
--- a/Android.bp
+++ b/Android.bp
@@ -99,6 +99,7 @@
         ":android.hardware.biometrics.common-V4-java-source",
         ":android.hardware.biometrics.fingerprint-V5-java-source",
         ":android.hardware.biometrics.fingerprint.virtualhal-java-source",
+        ":android.hardware.biometrics.face.virtualhal-java-source",
         ":android.hardware.biometrics.face-V4-java-source",
         ":android.hardware.gnss-V2-java-source",
         ":android.hardware.graphics.common-V3-java-source",
diff --git a/core/api/current.txt b/core/api/current.txt
index 542543d..f817241 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6495,6 +6495,7 @@
     field public static final int FLAG_NO_CLEAR = 32; // 0x20
     field public static final int FLAG_ONGOING_EVENT = 2; // 0x2
     field public static final int FLAG_ONLY_ALERT_ONCE = 8; // 0x8
+    field @FlaggedApi("android.app.api_rich_ongoing") public static final int FLAG_PROMOTED_ONGOING = 262144; // 0x40000
     field @Deprecated public static final int FLAG_SHOW_LIGHTS = 1; // 0x1
     field public static final int FOREGROUND_SERVICE_DEFAULT = 0; // 0x0
     field public static final int FOREGROUND_SERVICE_DEFERRED = 2; // 0x2
@@ -19396,7 +19397,7 @@
   public abstract static class CameraExtensionSession.ExtensionCaptureCallback {
     ctor public CameraExtensionSession.ExtensionCaptureCallback();
     method public void onCaptureFailed(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureFailed(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest, int);
+    method public void onCaptureFailed(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest, int);
     method public void onCaptureProcessProgressed(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest, @IntRange(from=0, to=100) int);
     method public void onCaptureProcessStarted(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest);
     method public void onCaptureResultAvailable(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest, @NonNull android.hardware.camera2.TotalCaptureResult);
@@ -19921,7 +19922,7 @@
     field @Deprecated @NonNull public static final android.hardware.camera2.CaptureResult.Key<float[]> LENS_RADIAL_DISTORTION;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> LENS_STATE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.String> LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID;
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Rect> LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_SENSOR_CROP_REGION;
+    field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Rect> LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_SENSOR_CROP_REGION;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> NOISE_REDUCTION_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Byte> REQUEST_PIPELINE_DEPTH;
@@ -19947,7 +19948,7 @@
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> STATISTICS_FACE_DETECT_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Point[]> STATISTICS_HOT_PIXEL_MAP;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> STATISTICS_HOT_PIXEL_MAP_MODE;
-    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.LensIntrinsicsSample[]> STATISTICS_LENS_INTRINSICS_SAMPLES;
+    field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.LensIntrinsicsSample[]> STATISTICS_LENS_INTRINSICS_SAMPLES;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.LensShadingMap> STATISTICS_LENS_SHADING_CORRECTION_MAP;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> STATISTICS_LENS_SHADING_MAP_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> STATISTICS_OIS_DATA_MODE;
@@ -20107,10 +20108,10 @@
     method public boolean isMultiResolution();
   }
 
-  @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class LensIntrinsicsSample {
-    ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public LensIntrinsicsSample(long, @NonNull float[]);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public float[] getLensIntrinsics();
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public long getTimestampNanos();
+  public final class LensIntrinsicsSample {
+    ctor public LensIntrinsicsSample(long, @NonNull float[]);
+    method @NonNull public float[] getLensIntrinsics();
+    method public long getTimestampNanos();
   }
 
   public final class LensShadingMap {
@@ -36934,14 +36935,16 @@
 
   @FlaggedApi("android.provider.new_default_account_api_enabled") public static final class ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState {
     ctor public ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState(int, @Nullable android.accounts.Account);
-    method @Nullable public android.accounts.Account getCloudAccount();
+    method @Nullable public android.accounts.Account getAccount();
     method public int getState();
     method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofCloud(@NonNull android.accounts.Account);
     method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofLocal();
     method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofNotSet();
+    method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofSim(@NonNull android.accounts.Account);
     field public static final int DEFAULT_ACCOUNT_STATE_CLOUD = 3; // 0x3
     field public static final int DEFAULT_ACCOUNT_STATE_LOCAL = 2; // 0x2
     field public static final int DEFAULT_ACCOUNT_STATE_NOT_SET = 1; // 0x1
+    field public static final int DEFAULT_ACCOUNT_STATE_SIM = 4; // 0x4
   }
 
   public static final class ContactsContract.RawContacts.DisplayPhoto {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b2a49e1..cc0354c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4950,94 +4950,94 @@
 
 package android.hardware.camera2.extension {
 
-  @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract class AdvancedExtender {
-    ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public AdvancedExtender(@NonNull android.hardware.camera2.CameraManager);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.List<android.hardware.camera2.CaptureRequest.Key> getAvailableCaptureRequestKeys(@NonNull String);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.List<android.hardware.camera2.CaptureResult.Key> getAvailableCaptureResultKeys(@NonNull String);
-    method @FlaggedApi("com.android.internal.camera.flags.camera_extensions_characteristics_get") @NonNull public abstract java.util.List<android.util.Pair<android.hardware.camera2.CameraCharacteristics.Key,java.lang.Object>> getAvailableCharacteristicsKeyValues();
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public long getMetadataVendorId(@NonNull String);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract android.hardware.camera2.extension.SessionProcessor getSessionProcessor();
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.Map<java.lang.Integer,java.util.List<android.util.Size>> getSupportedCaptureOutputResolutions(@NonNull String);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.Map<java.lang.Integer,java.util.List<android.util.Size>> getSupportedPreviewOutputResolutions(@NonNull String);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void initialize(@NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract boolean isExtensionAvailable(@NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap);
+  public abstract class AdvancedExtender {
+    ctor public AdvancedExtender(@NonNull android.hardware.camera2.CameraManager);
+    method @NonNull public abstract java.util.List<android.hardware.camera2.CaptureRequest.Key> getAvailableCaptureRequestKeys(@NonNull String);
+    method @NonNull public abstract java.util.List<android.hardware.camera2.CaptureResult.Key> getAvailableCaptureResultKeys(@NonNull String);
+    method @NonNull public abstract java.util.List<android.util.Pair<android.hardware.camera2.CameraCharacteristics.Key,java.lang.Object>> getAvailableCharacteristicsKeyValues();
+    method public long getMetadataVendorId(@NonNull String);
+    method @NonNull public abstract android.hardware.camera2.extension.SessionProcessor getSessionProcessor();
+    method @NonNull public abstract java.util.Map<java.lang.Integer,java.util.List<android.util.Size>> getSupportedCaptureOutputResolutions(@NonNull String);
+    method @NonNull public abstract java.util.Map<java.lang.Integer,java.util.List<android.util.Size>> getSupportedPreviewOutputResolutions(@NonNull String);
+    method public abstract void initialize(@NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap);
+    method public abstract boolean isExtensionAvailable(@NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap);
   }
 
-  @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract class CameraExtensionService extends android.app.Service {
-    ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") protected CameraExtensionService();
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract android.hardware.camera2.extension.AdvancedExtender onInitializeAdvancedExtension(int);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract boolean onRegisterClient(@NonNull android.os.IBinder);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void onUnregisterClient(@NonNull android.os.IBinder);
+  public abstract class CameraExtensionService extends android.app.Service {
+    ctor protected CameraExtensionService();
+    method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
+    method @NonNull public abstract android.hardware.camera2.extension.AdvancedExtender onInitializeAdvancedExtension(int);
+    method public abstract boolean onRegisterClient(@NonNull android.os.IBinder);
+    method public abstract void onUnregisterClient(@NonNull android.os.IBinder);
   }
 
-  @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class CameraOutputSurface {
-    ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public CameraOutputSurface(@NonNull android.view.Surface, @NonNull android.util.Size);
+  public final class CameraOutputSurface {
+    ctor public CameraOutputSurface(@NonNull android.view.Surface, @NonNull android.util.Size);
     method public int getColorSpace();
     method public long getDynamicRangeProfile();
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int getImageFormat();
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.util.Size getSize();
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.view.Surface getSurface();
+    method public int getImageFormat();
+    method @NonNull public android.util.Size getSize();
+    method @NonNull public android.view.Surface getSurface();
     method public void setDynamicRangeProfile(long);
   }
 
-  @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class CharacteristicsMap {
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @Nullable public android.hardware.camera2.CameraCharacteristics get(@NonNull String);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public java.util.Set<java.lang.String> getCameraIds();
+  public class CharacteristicsMap {
+    method @Nullable public android.hardware.camera2.CameraCharacteristics get(@NonNull String);
+    method @NonNull public java.util.Set<java.lang.String> getCameraIds();
   }
 
-  @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionConfiguration {
-    ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public ExtensionConfiguration(int, int, @NonNull java.util.List<android.hardware.camera2.extension.ExtensionOutputConfiguration>, @Nullable android.hardware.camera2.CaptureRequest);
+  public class ExtensionConfiguration {
+    ctor public ExtensionConfiguration(int, int, @NonNull java.util.List<android.hardware.camera2.extension.ExtensionOutputConfiguration>, @Nullable android.hardware.camera2.CaptureRequest);
     method public void setColorSpace(int);
   }
 
-  @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionOutputConfiguration {
-    ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public ExtensionOutputConfiguration(@NonNull java.util.List<android.hardware.camera2.extension.CameraOutputSurface>, int, @Nullable String, int);
+  public class ExtensionOutputConfiguration {
+    ctor public ExtensionOutputConfiguration(@NonNull java.util.List<android.hardware.camera2.extension.CameraOutputSurface>, int, @Nullable String, int);
   }
 
-  @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class RequestProcessor {
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void abortCaptures();
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int setRepeating(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException;
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void stopRepeating();
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int submit(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException;
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int submitBurst(@NonNull java.util.List<android.hardware.camera2.extension.RequestProcessor.Request>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException;
+  public final class RequestProcessor {
+    method public void abortCaptures();
+    method public int setRepeating(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException;
+    method public void stopRepeating();
+    method public int submit(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException;
+    method public int submitBurst(@NonNull java.util.List<android.hardware.camera2.extension.RequestProcessor.Request>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException;
   }
 
-  @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final class RequestProcessor.Request {
-    ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public RequestProcessor.Request(@NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>>, int);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>> getParameters();
+  public static final class RequestProcessor.Request {
+    ctor public RequestProcessor.Request(@NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>>, int);
+    method @NonNull public java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>> getParameters();
   }
 
-  @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static interface RequestProcessor.RequestCallback {
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureBufferLost(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, long, int);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureCompleted(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @Nullable android.hardware.camera2.TotalCaptureResult);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureFailed(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull android.hardware.camera2.CaptureFailure);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureProgressed(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull android.hardware.camera2.CaptureResult);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceAborted(int);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceCompleted(int, long);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureStarted(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, long, long);
+  public static interface RequestProcessor.RequestCallback {
+    method public void onCaptureBufferLost(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, long, int);
+    method public void onCaptureCompleted(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @Nullable android.hardware.camera2.TotalCaptureResult);
+    method public void onCaptureFailed(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull android.hardware.camera2.CaptureFailure);
+    method public void onCaptureProgressed(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull android.hardware.camera2.CaptureResult);
+    method public void onCaptureSequenceAborted(int);
+    method public void onCaptureSequenceCompleted(int, long);
+    method public void onCaptureStarted(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, long, long);
   }
 
-  @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract class SessionProcessor {
-    ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public SessionProcessor();
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void deInitSession(@NonNull android.os.IBinder);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract android.hardware.camera2.extension.ExtensionConfiguration initSession(@NonNull android.os.IBinder, @NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap, @NonNull android.hardware.camera2.extension.CameraOutputSurface, @NonNull android.hardware.camera2.extension.CameraOutputSurface);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void onCaptureSessionEnd();
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void onCaptureSessionStart(@NonNull android.hardware.camera2.extension.RequestProcessor, @NonNull String);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void setParameters(@NonNull android.hardware.camera2.CaptureRequest);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startMultiFrameCapture(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startRepeating(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startTrigger(@NonNull android.hardware.camera2.CaptureRequest, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void stopRepeating();
+  public abstract class SessionProcessor {
+    ctor public SessionProcessor();
+    method public abstract void deInitSession(@NonNull android.os.IBinder);
+    method @NonNull public abstract android.hardware.camera2.extension.ExtensionConfiguration initSession(@NonNull android.os.IBinder, @NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap, @NonNull android.hardware.camera2.extension.CameraOutputSurface, @NonNull android.hardware.camera2.extension.CameraOutputSurface);
+    method public abstract void onCaptureSessionEnd();
+    method public abstract void onCaptureSessionStart(@NonNull android.hardware.camera2.extension.RequestProcessor, @NonNull String);
+    method public abstract void setParameters(@NonNull android.hardware.camera2.CaptureRequest);
+    method public abstract int startMultiFrameCapture(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback);
+    method public abstract int startRepeating(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback);
+    method public abstract int startTrigger(@NonNull android.hardware.camera2.CaptureRequest, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback);
+    method public abstract void stopRepeating();
   }
 
-  @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static interface SessionProcessor.CaptureCallback {
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureCompleted(long, int, @NonNull java.util.Map<android.hardware.camera2.CaptureResult.Key,java.lang.Object>);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureFailed(int, int);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureProcessStarted(int);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceAborted(int);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceCompleted(int);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureStarted(int, long);
+  public static interface SessionProcessor.CaptureCallback {
+    method public void onCaptureCompleted(long, int, @NonNull java.util.Map<android.hardware.camera2.CaptureResult.Key,java.lang.Object>);
+    method public void onCaptureFailed(int, int);
+    method public void onCaptureProcessStarted(int);
+    method public void onCaptureSequenceAborted(int);
+    method public void onCaptureSequenceCompleted(int);
+    method public void onCaptureStarted(int, long);
   }
 
 }
diff --git a/core/java/android/app/BroadcastStickyCache.java b/core/java/android/app/BroadcastStickyCache.java
new file mode 100644
index 0000000..d6f061b
--- /dev/null
+++ b/core/java/android/app/BroadcastStickyCache.java
@@ -0,0 +1,229 @@
+/*
+ * 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.app;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbManager;
+import android.media.AudioManager;
+import android.net.ConnectivityManager;
+import android.net.TetheringManager;
+import android.net.nsd.NsdManager;
+import android.net.wifi.WifiManager;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.os.SystemProperties;
+import android.os.UpdateLock;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
+import android.view.WindowManagerPolicyConstants;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+
+/** @hide */
+public class BroadcastStickyCache {
+
+    private static final String[] CACHED_BROADCAST_ACTIONS = {
+            AudioManager.ACTION_HDMI_AUDIO_PLUG,
+            AudioManager.ACTION_HEADSET_PLUG,
+            AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED,
+            AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED,
+            AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+            AudioManager.RINGER_MODE_CHANGED_ACTION,
+            ConnectivityManager.CONNECTIVITY_ACTION,
+            Intent.ACTION_BATTERY_CHANGED,
+            Intent.ACTION_DEVICE_STORAGE_FULL,
+            Intent.ACTION_DEVICE_STORAGE_LOW,
+            Intent.ACTION_SIM_STATE_CHANGED,
+            NsdManager.ACTION_NSD_STATE_CHANGED,
+            TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED,
+            TetheringManager.ACTION_TETHER_STATE_CHANGED,
+            UpdateLock.UPDATE_LOCK_CHANGED,
+            UsbManager.ACTION_USB_STATE,
+            WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED,
+            WifiManager.NETWORK_STATE_CHANGED_ACTION,
+            WifiManager.SUPPLICANT_STATE_CHANGED_ACTION,
+            WifiManager.WIFI_STATE_CHANGED_ACTION,
+            WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION,
+            WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED,
+            "android.net.conn.INET_CONDITION_ACTION" // ConnectivityManager.INET_CONDITION_ACTION
+    };
+
+    @GuardedBy("sCachedStickyBroadcasts")
+    private static final ArrayList<CachedStickyBroadcast> sCachedStickyBroadcasts =
+            new ArrayList<>();
+
+    @GuardedBy("sCachedPropertyHandles")
+    private static final ArrayMap<String, SystemProperties.Handle> sCachedPropertyHandles =
+            new ArrayMap<>();
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static boolean useCache(@Nullable IntentFilter filter) {
+        if (!shouldCache(filter)) {
+            return false;
+        }
+        synchronized (sCachedStickyBroadcasts) {
+            final CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter);
+            if (cachedStickyBroadcast == null) {
+                return false;
+            }
+            final long version = cachedStickyBroadcast.propertyHandle.getLong(-1 /* def */);
+            return version > 0 && cachedStickyBroadcast.version == version;
+        }
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static void add(@Nullable IntentFilter filter, @Nullable Intent intent) {
+        if (!shouldCache(filter)) {
+            return;
+        }
+        synchronized (sCachedStickyBroadcasts) {
+            CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter);
+            if (cachedStickyBroadcast == null) {
+                final String key = getKey(filter.getAction(0));
+                final SystemProperties.Handle handle = SystemProperties.find(key);
+                final long version = handle == null ? -1 : handle.getLong(-1 /* def */);
+                if (version == -1) {
+                    return;
+                }
+                cachedStickyBroadcast = new CachedStickyBroadcast(filter, handle);
+                sCachedStickyBroadcasts.add(cachedStickyBroadcast);
+                cachedStickyBroadcast.intent = intent;
+                cachedStickyBroadcast.version = version;
+            } else {
+                cachedStickyBroadcast.intent = intent;
+                cachedStickyBroadcast.version = cachedStickyBroadcast.propertyHandle
+                        .getLong(-1 /* def */);
+            }
+        }
+    }
+
+    private static boolean shouldCache(@Nullable IntentFilter filter) {
+        if (!Flags.useStickyBcastCache()) {
+            return false;
+        }
+        if (filter == null || filter.safeCountActions() != 1) {
+            return false;
+        }
+        if (!ArrayUtils.contains(CACHED_BROADCAST_ACTIONS, filter.getAction(0))) {
+            return false;
+        }
+        return true;
+    }
+
+    @VisibleForTesting
+    @NonNull
+    public static String getKey(@NonNull String action) {
+        return "cache_key.system_server.sticky_bcast." + action;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @Nullable
+    public static Intent getIntentUnchecked(@NonNull IntentFilter filter) {
+        synchronized (sCachedStickyBroadcasts) {
+            final CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter);
+            return cachedStickyBroadcast.intent;
+        }
+    }
+
+    @GuardedBy("sCachedStickyBroadcasts")
+    @Nullable
+    private static CachedStickyBroadcast getValueUncheckedLocked(@NonNull IntentFilter filter) {
+        for (int i = sCachedStickyBroadcasts.size() - 1; i >= 0; --i) {
+            final CachedStickyBroadcast cachedStickyBroadcast = sCachedStickyBroadcasts.get(i);
+            if (IntentFilter.filterEquals(filter, cachedStickyBroadcast.filter)) {
+                return cachedStickyBroadcast;
+            }
+        }
+        return null;
+    }
+
+    public static void incrementVersion(@NonNull String action) {
+        if (!shouldIncrementVersion(action)) {
+            return;
+        }
+        final String key = getKey(action);
+        synchronized (sCachedPropertyHandles) {
+            SystemProperties.Handle handle = sCachedPropertyHandles.get(key);
+            final long version;
+            if (handle == null) {
+                handle = SystemProperties.find(key);
+                if (handle != null) {
+                    sCachedPropertyHandles.put(key, handle);
+                }
+            }
+            version = handle == null ? 0 : handle.getLong(0 /* def */);
+            SystemProperties.set(key, String.valueOf(version + 1));
+            if (handle == null) {
+                sCachedPropertyHandles.put(key, SystemProperties.find(key));
+            }
+        }
+    }
+
+    public static void incrementVersionIfExists(@NonNull String action) {
+        if (!shouldIncrementVersion(action)) {
+            return;
+        }
+        final String key = getKey(action);
+        synchronized (sCachedPropertyHandles) {
+            final SystemProperties.Handle handle = sCachedPropertyHandles.get(key);
+            if (handle == null) {
+                return;
+            }
+            final long version = handle.getLong(0 /* def */);
+            SystemProperties.set(key, String.valueOf(version + 1));
+        }
+    }
+
+    private static boolean shouldIncrementVersion(@NonNull String action) {
+        if (!Flags.useStickyBcastCache()) {
+            return false;
+        }
+        if (!ArrayUtils.contains(CACHED_BROADCAST_ACTIONS, action)) {
+            return false;
+        }
+        return true;
+    }
+
+    @VisibleForTesting
+    public static void clearForTest() {
+        synchronized (sCachedStickyBroadcasts) {
+            sCachedStickyBroadcasts.clear();
+        }
+        synchronized (sCachedPropertyHandles) {
+            sCachedPropertyHandles.clear();
+        }
+    }
+
+    private static final class CachedStickyBroadcast {
+        @NonNull public final IntentFilter filter;
+        @Nullable public Intent intent;
+        @IntRange(from = 0) public long version;
+        @NonNull public final SystemProperties.Handle propertyHandle;
+
+        CachedStickyBroadcast(@NonNull IntentFilter filter,
+                @NonNull SystemProperties.Handle propertyHandle) {
+            this.filter = filter;
+            this.propertyHandle = propertyHandle;
+        }
+    }
+}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 90fba29..ecef0db 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1921,10 +1921,19 @@
             }
         }
         try {
-            final Intent intent = ActivityManager.getService().registerReceiverWithFeature(
-                    mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
-                    AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId,
-                    flags);
+            final Intent intent;
+            if (receiver == null && BroadcastStickyCache.useCache(filter)) {
+                intent = BroadcastStickyCache.getIntentUnchecked(filter);
+            } else {
+                intent = ActivityManager.getService().registerReceiverWithFeature(
+                        mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
+                        AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission,
+                        userId,
+                        flags);
+                if (receiver == null) {
+                    BroadcastStickyCache.add(filter, intent);
+                }
+            }
             if (intent != null) {
                 intent.setExtrasClassLoader(getClassLoader());
                 // TODO: determine at registration time if caller is
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index e2bee64..b9fe356 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -258,4 +258,6 @@
     @EnforcePermission(allOf={"INTERACT_ACROSS_USERS", "ACCESS_NOTIFICATIONS"})
     void unregisterCallNotificationEventListener(String packageName, in UserHandle userHandle, in ICallNotificationEventCallback listener);
 
+    void setCanBePromoted(String pkg, int uid, boolean promote);
+    boolean canBePromoted(String pkg, int uid);
 }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 81d2c89..4d73c35 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -772,6 +772,17 @@
     @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
     public static final int FLAG_SILENT = 1 << 17;  //0x00020000
 
+    /**
+     * Bit to be bitwise-ored into the {@link #flags} field that should be
+     * set by the system if this notification is a promoted ongoing notification, either via a
+     * user setting or allowlist.
+     *
+     * Applications cannot set this flag directly, but the posting app and
+     * {@link android.service.notification.NotificationListenerService} can read it.
+     */
+    @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+    public static final int FLAG_PROMOTED_ONGOING = 0x00040000;
+
     private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
             BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
             DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
@@ -3110,6 +3121,53 @@
     }
 
     /**
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
+    public boolean containsCustomViews() {
+        return contentView != null
+                || bigContentView != null
+                || headsUpContentView != null
+                || (publicVersion != null
+                && (publicVersion.contentView != null
+                || publicVersion.bigContentView != null
+                || publicVersion.headsUpContentView != null));
+    }
+
+    /**
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
+    public boolean hasTitle() {
+        return extras != null
+                && (!TextUtils.isEmpty(extras.getCharSequence(EXTRA_TITLE))
+                || !TextUtils.isEmpty(extras.getCharSequence(EXTRA_TITLE_BIG)));
+    }
+
+    /**
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
+    public boolean hasPromotableStyle() {
+        //TODO(b/367739672): Add progress style
+        return extras == null || !extras.containsKey(Notification.EXTRA_TEMPLATE)
+                || isStyle(Notification.BigPictureStyle.class)
+                || isStyle(Notification.BigTextStyle.class)
+                || isStyle(Notification.CallStyle.class);
+    }
+
+    /**
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
+    public boolean hasPromotableCharacteristics() {
+        return isColorized()
+                && hasTitle()
+                && !containsCustomViews()
+                && hasPromotableStyle();
+    }
+
+    /**
      * Whether this notification was posted by a headless system app.
      *
      * If we don't have enough information to figure this out, this will return false. Therefore,
@@ -7636,7 +7694,6 @@
 
         if (mLargeIcon != null || largeIcon != null) {
             Resources resources = context.getResources();
-            Class<? extends Style> style = getNotificationStyle();
             int maxSize = resources.getDimensionPixelSize(isLowRam
                     ? R.dimen.notification_right_icon_size_low_ram
                     : R.dimen.notification_right_icon_size);
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 5ed1f4e..637187e 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -177,6 +177,10 @@
         {
             "file_patterns": ["(/|^)AppOpsManager.java"],
             "name": "CtsAppOpsTestCases"
+        },
+        {
+            "file_patterns": ["(/|^)BroadcastStickyCache.java"],
+            "name": "BroadcastUnitTests"
         }
     ]
 }
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index 38bd576..56488e7 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -147,3 +147,14 @@
          purpose: PURPOSE_BUGFIX
      }
 }
+
+flag {
+     namespace: "backstage_power"
+     name: "use_sticky_bcast_cache"
+     description: "Use cache for sticky broadcast intents"
+     is_fixed_read_only: true
+     bug: "356148006"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 9b87df9..3cf508a 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -259,10 +259,8 @@
         private static final String PROXY_SERVICE_NAME =
                 "com.android.cameraextensions.CameraExtensionsProxyService";
 
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         private static final int FALLBACK_PACKAGE_NAME =
                 com.android.internal.R.string.config_extensionFallbackPackageName;
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         private static final int FALLBACK_SERVICE_NAME =
                 com.android.internal.R.string.config_extensionFallbackServiceName;
 
@@ -309,7 +307,7 @@
                   intent.setClassName(vendorProxyPackage, vendorProxyService);
                 }
 
-                if (Flags.concertMode() && useFallback) {
+                if (useFallback) {
                     String packageName = ctx.getResources().getString(FALLBACK_PACKAGE_NAME);
                     String serviceName = ctx.getResources().getString(FALLBACK_SERVICE_NAME);
 
@@ -433,7 +431,7 @@
                     releaseProxyConnectionLocked(ctx, extension);
                 }
 
-                if (Flags.concertMode() && ret && useFallback && mIsFallbackEnabled) {
+                if (ret && useFallback && mIsFallbackEnabled) {
                     try {
                         InitializeSessionHandler cb = new InitializeSessionHandler(ctx);
                         initializeSession(cb, extension);
@@ -462,26 +460,24 @@
 
             boolean ret = registerClientHelper(ctx, token, extension, false /*useFallback*/);
 
-            if (Flags.concertMode()) {
-                // Check if user enabled fallback impl
-                ContentResolver resolver = ctx.getContentResolver();
-                int userEnabled = Settings.Secure.getInt(resolver,
-                        Settings.Secure.CAMERA_EXTENSIONS_FALLBACK, 1);
+            // Check if user enabled fallback impl
+            ContentResolver resolver = ctx.getContentResolver();
+            int userEnabled = Settings.Secure.getInt(resolver,
+                    Settings.Secure.CAMERA_EXTENSIONS_FALLBACK, 1);
 
-                boolean vendorImpl = true;
-                if (ret && (mConnectionManager.getProxy(extension) != null) && (userEnabled == 1)) {
-                    // At this point, we are connected to either CameraExtensionsProxyService or
-                    // the vendor extension proxy service. If the vendor does not support the
-                    // extension, unregisterClient and re-register client with the proxy service
-                    // containing the fallback impl
-                    vendorImpl = isExtensionSupported(cameraId, extension,
-                            characteristicsMapNative);
-                }
+            boolean vendorImpl = true;
+            if (ret && (mConnectionManager.getProxy(extension) != null) && (userEnabled == 1)) {
+                // At this point, we are connected to either CameraExtensionsProxyService or
+                // the vendor extension proxy service. If the vendor does not support the
+                // extension, unregisterClient and re-register client with the proxy service
+                // containing the fallback impl
+                vendorImpl = isExtensionSupported(cameraId, extension,
+                        characteristicsMapNative);
+            }
 
-                if (!vendorImpl) {
-                    unregisterClient(ctx, token, extension);
-                    ret = registerClientHelper(ctx, token, extension, true /*useFallback*/);
-                }
+            if (!vendorImpl) {
+                unregisterClient(ctx, token, extension);
+                ret = registerClientHelper(ctx, token, extension, true /*useFallback*/);
             }
 
             return ret;
diff --git a/core/java/android/hardware/camera2/CameraExtensionSession.java b/core/java/android/hardware/camera2/CameraExtensionSession.java
index 2d9433e..20f89a5 100644
--- a/core/java/android/hardware/camera2/CameraExtensionSession.java
+++ b/core/java/android/hardware/camera2/CameraExtensionSession.java
@@ -156,7 +156,6 @@
          * @see #capture
          * @see #setRepeatingRequest
          */
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         public void onCaptureFailed(@NonNull CameraExtensionSession session,
                 @NonNull CaptureRequest request, @CaptureFailure.FailureReason int failure) {
             // default empty implementation
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 75d617c..a18a634 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -5293,7 +5293,6 @@
     @PublicKey
     @NonNull
     @SyntheticKey
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public static final Key<android.hardware.camera2.params.LensIntrinsicsSample[]> STATISTICS_LENS_INTRINSICS_SAMPLES =
             new Key<android.hardware.camera2.params.LensIntrinsicsSample[]>("android.statistics.lensIntrinsicsSamples", android.hardware.camera2.params.LensIntrinsicsSample[].class);
 
@@ -5307,7 +5306,6 @@
      * @see CaptureResult#SENSOR_TIMESTAMP
      * @hide
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public static final Key<long[]> STATISTICS_LENS_INTRINSIC_TIMESTAMPS =
             new Key<long[]>("android.statistics.lensIntrinsicTimestamps", long[].class);
 
@@ -5323,7 +5321,6 @@
      * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
      * @hide
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public static final Key<float[]> STATISTICS_LENS_INTRINSIC_SAMPLES =
             new Key<float[]>("android.statistics.lensIntrinsicSamples", float[].class);
 
@@ -5814,7 +5811,6 @@
      */
     @PublicKey
     @NonNull
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public static final Key<android.graphics.Rect> LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_SENSOR_CROP_REGION =
             new Key<android.graphics.Rect>("android.logicalMultiCamera.activePhysicalSensorCropRegion", android.graphics.Rect.class);
 
diff --git a/core/java/android/hardware/camera2/extension/AdvancedExtender.java b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
index 8fa09a8..df66f59 100644
--- a/core/java/android/hardware/camera2/extension/AdvancedExtender.java
+++ b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
@@ -53,7 +53,6 @@
  * @hide
  */
 @SystemApi
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
 public abstract class AdvancedExtender {
     private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>();
     private final CameraManager mCameraManager;
@@ -66,7 +65,6 @@
      *
      * @param cameraManager the system camera manager
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public AdvancedExtender(@NonNull CameraManager cameraManager) {
         mCameraManager = cameraManager;
         try {
@@ -101,7 +99,6 @@
      * @param cameraId           The camera2 id string of the camera.
      * @return the camera metadata vendor Id associated with the given camera
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public long getMetadataVendorId(@NonNull String cameraId) {
         long vendorId = mMetadataVendorIdMap.containsKey(cameraId) ?
                 mMetadataVendorIdMap.get(cameraId) : Long.MAX_VALUE;
@@ -123,7 +120,6 @@
      *                           CameraCharacteristics.
      * @return true if the extension is supported, otherwise false
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public abstract boolean isExtensionAvailable(@NonNull String cameraId,
             @NonNull CharacteristicsMap charsMap);
 
@@ -144,7 +140,6 @@
      *                           physical camera ids and their
      *                           CameraCharacteristics.
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public abstract void initialize(@NonNull String cameraId, @NonNull CharacteristicsMap map);
 
     /**
@@ -159,7 +154,6 @@
      * be identical to the supported preview output format returned here.
      * @param cameraId           The camera2 id string of the camera.
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     @NonNull
     public abstract Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(
             @NonNull String cameraId);
@@ -179,7 +173,6 @@
      * writes the output to the output surface.
      * @param cameraId           The camera2 id string of the camera.
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     @NonNull
     public abstract Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(
             @NonNull String cameraId);
@@ -189,7 +182,6 @@
      * implements all the interactions required for starting an extension
      * and cleanup.
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     @NonNull
     public abstract SessionProcessor getSessionProcessor();
 
@@ -227,7 +219,6 @@
      * @return The list of supported orthogonal capture keys, or empty
      * list if no capture settings are not supported.
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     @NonNull
     public abstract List<CaptureRequest.Key> getAvailableCaptureRequestKeys(
             @NonNull String cameraId);
@@ -245,7 +236,6 @@
      * @return The list of supported capture result keys, or
      * empty list if capture results are not supported.
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     @NonNull
     public abstract List<CaptureResult.Key> getAvailableCaptureResultKeys(
             @NonNull String cameraId);
@@ -270,7 +260,6 @@
      * {@link CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP} and
      *  {@link CameraCharacteristics#REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP}.
      */
-    @FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET)
     @NonNull
     public abstract List<Pair<CameraCharacteristics.Key, Object>>
             getAvailableCharacteristicsKeyValues();
@@ -377,7 +366,6 @@
             return false;
         }
 
-        @FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET)
         @Override
         public CameraMetadataNative getAvailableCharacteristicsKeyValues(String cameraId) {
             List<Pair<CameraCharacteristics.Key, Object>> entries =
diff --git a/core/java/android/hardware/camera2/extension/CameraExtensionService.java b/core/java/android/hardware/camera2/extension/CameraExtensionService.java
index 01698d5..9cc4f16 100644
--- a/core/java/android/hardware/camera2/extension/CameraExtensionService.java
+++ b/core/java/android/hardware/camera2/extension/CameraExtensionService.java
@@ -42,7 +42,6 @@
  * @hide
  */
 @SystemApi
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
 public abstract class CameraExtensionService extends Service {
     private static final String TAG = "CameraExtensionService";
     private CameraUsageTracker mCameraUsageTracker;
@@ -87,10 +86,8 @@
         }
     };
 
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     protected CameraExtensionService() { }
 
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     @Override
     @NonNull
     public final IBinder onBind(@Nullable Intent intent) {
@@ -186,7 +183,6 @@
      *                           unexpectedly.
      * @return true if the registration is successful, false otherwise
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public abstract boolean onRegisterClient(@NonNull IBinder token);
 
     /**
@@ -194,7 +190,6 @@
      *
      * @param token              Binder token
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public abstract void onUnregisterClient(@NonNull IBinder token);
 
     /**
@@ -204,7 +199,6 @@
      *                      extension type
      * @return Valid advanced extender of the requested type
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     @NonNull
     public abstract AdvancedExtender onInitializeAdvancedExtension(@Extension int extensionType);
 }
diff --git a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
index 32139b8..e8b20e3 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
+++ b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
@@ -42,11 +42,9 @@
  * @hide
  */
 @SystemApi
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
 public final class CameraOutputSurface {
     private final OutputSurface mOutputSurface;
 
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     CameraOutputSurface(@NonNull OutputSurface surface) {
        mOutputSurface = surface;
     }
@@ -59,7 +57,6 @@
      * @param size         Requested size of the camera
      *                     output
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public CameraOutputSurface(@NonNull Surface surface,
             @NonNull Size size) {
         mOutputSurface = new OutputSurface();
@@ -75,7 +72,6 @@
     /**
      * Return the current output {@link Surface}
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     @NonNull
     public Surface getSurface() {
         return mOutputSurface.surface;
@@ -84,7 +80,6 @@
     /**
      * Return the current requested output size
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     @NonNull
     public android.util.Size getSize() {
         if (mOutputSurface.size != null) {
@@ -96,7 +91,6 @@
     /**
      * Return the current surface output {@link android.graphics.ImageFormat}
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public @ImageFormat.Format int getImageFormat() {
         return mOutputSurface.imageFormat;
     }
diff --git a/core/java/android/hardware/camera2/extension/CharacteristicsMap.java b/core/java/android/hardware/camera2/extension/CharacteristicsMap.java
index 495abc8..e578f6e 100644
--- a/core/java/android/hardware/camera2/extension/CharacteristicsMap.java
+++ b/core/java/android/hardware/camera2/extension/CharacteristicsMap.java
@@ -36,7 +36,6 @@
  * @hide
  */
 @SystemApi
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
 public class CharacteristicsMap {
     private final HashMap<String, CameraCharacteristics> mCharMap;
 
@@ -46,7 +45,6 @@
      * @param charsMap       Maps camera ids to respective
      *                       {@link CameraCharacteristics}
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     CharacteristicsMap(@NonNull Map<String, CameraMetadataNative> charsMap) {
         mCharMap = new HashMap<>();
         for (Map.Entry<String, CameraMetadataNative> entry : charsMap.entrySet()) {
@@ -59,7 +57,6 @@
      *
      * @return Set of the camera ids stored in the map
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     @NonNull
     public Set<String> getCameraIds() {
         return mCharMap.keySet();
@@ -74,7 +71,6 @@
      * @return Valid {@link CameraCharacteristics} instance of null
      *         in case the camera id is not part of the map
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     @Nullable
     public CameraCharacteristics get(@NonNull String cameraId) {
         return mCharMap.get(cameraId);
diff --git a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
index 32de1ce..43dfaa5 100644
--- a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
+++ b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
@@ -43,7 +43,6 @@
  * @hide
  */
 @SystemApi
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
 public class ExtensionConfiguration {
     private final int mSessionType;
     private final int mSessionTemplateId;
@@ -65,7 +64,6 @@
      * @param sessionParams     An optional set of camera capture
      *                          session parameter values
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public ExtensionConfiguration(@CameraDevice.SessionOperatingMode int sessionType,
             @CameraDevice.RequestTemplate int sessionTemplateId,
             @NonNull List<ExtensionOutputConfiguration> outputs,
@@ -87,7 +85,6 @@
         mColorSpace = colorSpace;
     }
 
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     CameraSessionConfig getCameraSessionConfig() {
         if (mOutputs.isEmpty()) {
             return null;
diff --git a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
index 8a47430..ff0d8d7b 100644
--- a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
+++ b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
@@ -35,7 +35,6 @@
  * @hide
  */
 @SystemApi
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
 public class ExtensionOutputConfiguration {
     private final List<CameraOutputSurface> mSurfaces;
     private final String mPhysicalCameraId;
@@ -57,7 +56,6 @@
      * @param surfaceGroupId    In case of surface group, this field must
      *                          contain the surface group id
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public ExtensionOutputConfiguration(@NonNull List<CameraOutputSurface> outputs,
             int outputConfigId, @Nullable String physicalCameraId, int surfaceGroupId) {
         mSurfaces = outputs;
diff --git a/core/java/android/hardware/camera2/extension/RequestProcessor.java b/core/java/android/hardware/camera2/extension/RequestProcessor.java
index 0ad27c2..936d57b 100644
--- a/core/java/android/hardware/camera2/extension/RequestProcessor.java
+++ b/core/java/android/hardware/camera2/extension/RequestProcessor.java
@@ -45,19 +45,16 @@
  * @hide
  */
 @SystemApi
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
 public final class RequestProcessor {
     private final static String TAG = "RequestProcessor";
     private final IRequestProcessorImpl mRequestProcessor;
     private final long mVendorId;
 
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     RequestProcessor (@NonNull IRequestProcessorImpl requestProcessor, long vendorId) {
         mRequestProcessor = requestProcessor;
         mVendorId = vendorId;
     }
 
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public interface RequestCallback {
         /**
          * This method is called when the camera device has started
@@ -76,7 +73,6 @@
          * @param frameNumber the frame number for this capture
          *
          */
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         void onCaptureStarted(@NonNull Request request, long frameNumber, long timestamp);
 
         /**
@@ -117,7 +113,6 @@
          *                      which includes a subset of the {@link
          *                      TotalCaptureResult} fields.
          */
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         void onCaptureProgressed(@NonNull Request request, @NonNull CaptureResult partialResult);
 
         /**
@@ -138,7 +133,6 @@
          *                           parameters and the state of the camera
          *                           system during capture.
          */
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         void onCaptureCompleted(@NonNull Request request,
                 @Nullable TotalCaptureResult totalCaptureResult);
 
@@ -163,7 +157,6 @@
          * @param failure The output failure from the capture, including the
          *                failure reason and the frame number.
          */
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         void onCaptureFailed(@NonNull Request request, @NonNull CaptureFailure failure);
 
         /**
@@ -182,7 +175,6 @@
          * @param outputStreamId The output stream id that the buffer will not
          *                       be produced for
          */
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         void onCaptureBufferLost(@NonNull Request request, long frameNumber, int outputStreamId);
 
         /**
@@ -203,7 +195,6 @@
          *                    or {@link CaptureFailure#getFrameNumber}) in
          *                    the capture sequence.
          */
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         void onCaptureSequenceCompleted(int sequenceId, long frameNumber);
 
         /**
@@ -221,17 +212,14 @@
          * @param sequenceId A sequence ID returned by the RequestProcessor
          *                   capture family of methods
          */
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         void onCaptureSequenceAborted(int sequenceId);
     }
 
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public final static class Request {
         private final List<Integer> mOutputIds;
         private final List<Pair<CaptureRequest.Key, Object>> mParameters;
         private final @CameraDevice.RequestTemplate int mTemplateId;
 
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         public Request(@NonNull List<Integer> outputConfigIds,
                 @NonNull List<Pair<CaptureRequest.Key, Object>> parameters,
                 @CameraDevice.RequestTemplate int templateId) {
@@ -244,7 +232,6 @@
          * Gets the target ids of {@link ExtensionOutputConfiguration} which identifies
          * corresponding Surface to be the targeted for the request.
          */
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         @NonNull
         List<Integer> getOutputConfigIds() {
             return mOutputIds;
@@ -253,7 +240,6 @@
         /**
          * Gets all the parameters.
          */
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         @NonNull
         public List<Pair<CaptureRequest.Key, Object>> getParameters() {
             return mParameters;
@@ -265,7 +251,6 @@
          *
          * @see CameraDevice.RequestTemplate
          */
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         Integer getTemplateId() {
             return mTemplateId;
         }
@@ -322,7 +307,6 @@
      * @param callback Request callback implementation
      * @return the id of the capture sequence
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public int submit(@NonNull Request request, @NonNull Executor executor,
             @NonNull RequestCallback callback) throws CameraAccessException {
         ArrayList<Request> requests = new ArrayList<>(1);
@@ -355,7 +339,6 @@
      * @param callback Request callback implementation
      * @return the id of the capture sequence
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public int submitBurst(@NonNull List<Request> requests, @NonNull Executor executor,
             @NonNull RequestCallback callback) throws CameraAccessException {
         List<android.hardware.camera2.extension.Request> parcelableRequests =
@@ -386,7 +369,6 @@
      * @param callback Request callback implementation
      * @return the id of the capture sequence
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public int setRepeating(@NonNull Request request, @NonNull Executor executor,
             @NonNull RequestCallback callback) throws CameraAccessException {
         ArrayList<Request> requests = new ArrayList<>(1);
@@ -413,7 +395,6 @@
     /**
      * Abort all ongoing capture requests.
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public void abortCaptures() {
         try {
             mRequestProcessor.abortCaptures();
@@ -425,7 +406,6 @@
     /**
      * Stop the current repeating request.
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public void stopRepeating() {
         try {
             mRequestProcessor.stopRepeating();
diff --git a/core/java/android/hardware/camera2/extension/SessionProcessor.java b/core/java/android/hardware/camera2/extension/SessionProcessor.java
index eead4ef..cf9f30d 100644
--- a/core/java/android/hardware/camera2/extension/SessionProcessor.java
+++ b/core/java/android/hardware/camera2/extension/SessionProcessor.java
@@ -76,7 +76,6 @@
  * @hide
  */
 @SystemApi
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
 public abstract class SessionProcessor {
     private static final String TAG = "SessionProcessor";
     private CameraUsageTracker mCameraUsageTracker;
@@ -84,7 +83,6 @@
     /**
      * Initialize a session process instance
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public SessionProcessor() {}
 
     void setCameraUsageTracker(CameraUsageTracker tracker) {
@@ -97,7 +95,6 @@
      * @hide
      */
     @SystemApi
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public interface CaptureCallback {
         /**
          * This method is called when the camera device has started
@@ -116,7 +113,6 @@
          *                          first frame in a multi-frame capture,
          *                          in nanoseconds.
          */
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         void onCaptureStarted(int captureSequenceId, long timestamp);
 
         /**
@@ -126,7 +122,6 @@
          *
          * @param captureSequenceId id of the current capture sequence
          */
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         void onCaptureProcessStarted(int captureSequenceId);
 
         /**
@@ -139,7 +134,6 @@
          * @param captureSequenceId id of the current capture sequence
          * @param failure           The capture failure reason
          */
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         void onCaptureFailed(int captureSequenceId, @CaptureFailure.FailureReason int failure);
 
         /**
@@ -154,7 +148,6 @@
          *
          * @param captureSequenceId id of the current capture sequence
          */
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         void onCaptureSequenceCompleted(int captureSequenceId);
 
         /**
@@ -162,7 +155,6 @@
          *
          * @param captureSequenceId id of the current capture sequence
          */
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         void onCaptureSequenceAborted(int captureSequenceId);
 
         /**
@@ -192,7 +184,6 @@
          *                  settings and results are always supported and
          *                  applied by the corresponding framework.
          */
-        @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         void onCaptureCompleted(long shutterTimestamp, int requestId,
                 @NonNull Map<CaptureResult.Key, Object> results);
     }
@@ -231,7 +222,6 @@
      * ensure this list will always  produce a valid camera capture
      * session.
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     @NonNull
     public abstract ExtensionConfiguration initSession(@NonNull IBinder token,
             @NonNull String cameraId, @NonNull CharacteristicsMap map,
@@ -247,7 +237,6 @@
      * @param token Binder token that can be used to unlink any previously
      *              linked death notifier callbacks
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public abstract void deInitSession(@NonNull IBinder token);
 
     /**
@@ -268,7 +257,6 @@
      *
      *
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public abstract void onCaptureSessionStart(@NonNull RequestProcessor requestProcessor,
             @NonNull String statsKey);
 
@@ -279,7 +267,6 @@
      * will no longer accept any requests after onCaptureSessionEnd()
      * returns.
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public abstract void onCaptureSessionEnd();
 
     /**
@@ -293,7 +280,6 @@
      * @param callback a callback to report the status.
      * @return the id of the capture sequence.
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public abstract int startRepeating(@NonNull Executor executor,
             @NonNull CaptureCallback callback);
 
@@ -305,7 +291,6 @@
      * forward calling {@link RequestProcessor#setRepeating} will simply
      * do nothing.
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public abstract void stopRepeating();
 
     /**
@@ -326,7 +311,6 @@
      * @param callback a callback to report the status.
      * @return the id of the capture sequence.
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public abstract int startMultiFrameCapture(@NonNull Executor executor,
             @NonNull CaptureCallback callback);
 
@@ -338,7 +322,6 @@
      * the value.
      *@param captureRequest Request that includes all client parameter
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public abstract void setParameters(@NonNull CaptureRequest captureRequest);
 
     /**
@@ -357,7 +340,6 @@
      * @return the id of the capture sequence.
      *
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public abstract int startTrigger(@NonNull CaptureRequest captureRequest,
             @NonNull Executor executor, @NonNull CaptureCallback callback);
 
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 323712d..fc03b51 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -873,17 +873,15 @@
 
         @Override
         public void onCaptureProcessFailed(int captureSequenceId, int captureFailureReason) {
-            if (Flags.concertMode()) {
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    mClientExecutor.execute(
-                            () -> mClientCallbacks.onCaptureFailed(
-                                     CameraAdvancedExtensionSessionImpl.this, mClientRequest,
-                                    captureFailureReason
-                            ));
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
-                }
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mClientExecutor.execute(
+                        () -> mClientCallbacks.onCaptureFailed(
+                                 CameraAdvancedExtensionSessionImpl.this, mClientRequest,
+                                captureFailureReason
+                        ));
+            } finally {
+                Binder.restoreCallingIdentity(ident);
             }
         }
 
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 0cd1c8c..ef7f3f8 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1797,57 +1797,49 @@
             return false;
         }
 
-        if (Flags.concertMode()) {
-            long[] tsArray = new long[samples.length];
-            float[] intrinsicsArray = new float[samples.length * 5];
-            for (int i = 0; i < samples.length; i++) {
-                tsArray[i] = samples[i].getTimestampNanos();
-                System.arraycopy(samples[i].getLensIntrinsics(), 0, intrinsicsArray, 5 * i, 5);
+        long[] tsArray = new long[samples.length];
+        float[] intrinsicsArray = new float[samples.length * 5];
+        for (int i = 0; i < samples.length; i++) {
+            tsArray[i] = samples[i].getTimestampNanos();
+            System.arraycopy(samples[i].getLensIntrinsics(), 0, intrinsicsArray, 5 * i, 5);
 
-            }
-            setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES, intrinsicsArray);
-            setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS, tsArray);
-
-            return true;
-        } else {
-            return false;
         }
+        setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES, intrinsicsArray);
+        setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS, tsArray);
+
+        return true;
     }
 
     private LensIntrinsicsSample[] getLensIntrinsicSamples() {
-        if (Flags.concertMode()) {
-            long[] timestamps = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS);
-            float[] intrinsics = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES);
+        long[] timestamps = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS);
+        float[] intrinsics = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES);
 
-            if (timestamps == null) {
-                if (intrinsics != null) {
-                    throw new AssertionError("timestamps is null but intrinsics is not");
-                }
-
-                return null;
+        if (timestamps == null) {
+            if (intrinsics != null) {
+                throw new AssertionError("timestamps is null but intrinsics is not");
             }
 
-            if (intrinsics == null) {
-                throw new AssertionError("timestamps is not null but intrinsics is");
-            } else if ((intrinsics.length % 5) != 0) {
-                throw new AssertionError("intrinsics are not multiple of 5");
-            }
-
-            if ((intrinsics.length / 5) != timestamps.length) {
-                throw new AssertionError(String.format(
-                        "timestamps has %d entries but intrinsics has %d", timestamps.length,
-                        intrinsics.length / 5));
-            }
-
-            LensIntrinsicsSample[] samples = new LensIntrinsicsSample[timestamps.length];
-            for (int i = 0; i < timestamps.length; i++) {
-                float[] currentIntrinsic = Arrays.copyOfRange(intrinsics, 5 * i, 5 * i + 5);
-                samples[i] = new LensIntrinsicsSample(timestamps[i], currentIntrinsic);
-            }
-            return samples;
-        } else {
             return null;
         }
+
+        if (intrinsics == null) {
+            throw new AssertionError("timestamps is not null but intrinsics is");
+        } else if ((intrinsics.length % 5) != 0) {
+            throw new AssertionError("intrinsics are not multiple of 5");
+        }
+
+        if ((intrinsics.length / 5) != timestamps.length) {
+            throw new AssertionError(String.format(
+                    "timestamps has %d entries but intrinsics has %d", timestamps.length,
+                    intrinsics.length / 5));
+        }
+
+        LensIntrinsicsSample[] samples = new LensIntrinsicsSample[timestamps.length];
+        for (int i = 0; i < timestamps.length; i++) {
+            float[] currentIntrinsic = Arrays.copyOfRange(intrinsics, 5 * i, 5 * i + 5);
+            samples[i] = new LensIntrinsicsSample(timestamps[i], currentIntrinsic);
+        }
+        return samples;
     }
 
     private Capability[] getExtendedSceneModeCapabilities() {
diff --git a/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java b/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java
index 9a4ec5c..9157ef10 100644
--- a/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java
+++ b/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java
@@ -31,7 +31,6 @@
  * Immutable class to store an
  * {@link CaptureResult#STATISTICS_LENS_INTRINSICS_SAMPLES lens intrinsics intra-frame sample}.
  */
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
 public final class LensIntrinsicsSample {
     /**
      * Create a new {@link LensIntrinsicsSample}.
@@ -46,7 +45,6 @@
      *
      * @throws IllegalArgumentException if lensIntrinsics length is different from 5
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public LensIntrinsicsSample(final long timestampNs, @NonNull final float[] lensIntrinsics) {
         mTimestampNs = timestampNs;
         Preconditions.checkArgument(lensIntrinsics.length == 5);
@@ -61,7 +59,6 @@
      *
      * @return a long value (guaranteed to be finite)
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public long getTimestampNanos() {
         return mTimestampNs;
     }
@@ -72,7 +69,6 @@
      * @return a floating point value (guaranteed to be finite)
      * @see CaptureResult#LENS_INTRINSIC_CALIBRATION
      */
-    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     @NonNull
     public float[] getLensIntrinsics() {
         return mLensIntrinsics;
diff --git a/core/java/android/hardware/face/FaceSensorConfigurations.java b/core/java/android/hardware/face/FaceSensorConfigurations.java
index 1247168..51c5f4c 100644
--- a/core/java/android/hardware/face/FaceSensorConfigurations.java
+++ b/core/java/android/hardware/face/FaceSensorConfigurations.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.SensorProps;
+import android.hardware.biometrics.face.virtualhal.IVirtualHal;
 import android.os.Binder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -160,6 +161,41 @@
         dest.writeByte((byte) (mResetLockoutRequiresChallenge ? 1 : 0));
         dest.writeMap(mSensorPropsMap);
     }
+    /**
+     * Remap fqName of VHAL because the `virtual` instance is registered
+     * with IVirtulalHal now (IFace previously)
+     * @param fqName fqName to be translated
+     * @return real fqName
+     */
+    public static String remapFqName(String fqName) {
+        if (!fqName.contains(IFace.DESCRIPTOR + "/virtual")) {
+            return fqName;  //no remap needed for real hardware HAL
+        } else {
+            //new Vhal instance name
+            return fqName.replace("IFace", "virtualhal.IVirtualHal");
+        }
+    }
+    /**
+     * @param fqName aidl interface instance name
+     * @return aidl interface
+     */
+    public static IFace getIFace(String fqName) {
+        if (fqName.contains("virtual")) {
+            String fqNameMapped = remapFqName(fqName);
+            Slog.i(TAG, "getIFace fqName is mapped: " + fqName + "->" + fqNameMapped);
+            try {
+                IVirtualHal vhal = IVirtualHal.Stub.asInterface(
+                        Binder.allowBlocking(ServiceManager.waitForService(fqNameMapped)));
+                return vhal.getFaceHal();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception in vhal.getFaceHal() call" + fqNameMapped);
+            }
+        }
+
+        return IFace.Stub.asInterface(
+                Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
+    }
+
 
     /**
      * Returns face sensor props for the HAL {@param instance}.
@@ -173,14 +209,13 @@
             return props;
         }
 
-        final String fqName = IFace.DESCRIPTOR + "/" + instance;
-        IFace face = IFace.Stub.asInterface(Binder.allowBlocking(
-                ServiceManager.waitForDeclaredService(fqName)));
         try {
-            if (face != null) {
-                props = face.getSensorProps();
+            final String fqName = IFace.DESCRIPTOR + "/" + instance;
+            final IFace fp = getIFace(fqName);
+            if (fp != null) {
+                props = fp.getSensorProps();
             } else {
-                Slog.e(TAG, "Unable to get declared service: " + fqName);
+                Log.d(TAG, "IFace null for instance " + instance);
             }
         } catch (RemoteException e) {
             Log.d(TAG, "Unable to get sensor properties!");
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index a622810..27b1dfb 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -3046,6 +3046,11 @@
              * <li> {@link #DEFAULT_ACCOUNT_STATE_CLOUD}: The default account is set to a
              * cloud-synced account. New raw contacts requested for insertion without a specified
              * {@link Account} will be saved in the default cloud account. </li>
+             * <li> {@link #DEFAULT_ACCOUNT_STATE_SIM}: The default account is set to a
+             * account that is associated with one of
+             * {@link SimContacts#getSimAccounts(ContentResolver)}. New raw contacts requested
+             * for insertion without a specified {@link Account} will be
+             * saved in this SIM account. </li>
              * </ul>
              */
             @FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED)
@@ -3063,44 +3068,51 @@
                 public static final int DEFAULT_ACCOUNT_STATE_CLOUD = 3;
 
                 /**
+                 * A state indicating that the default account is set as an account that is
+                 * associated with one of {@link SimContacts#getSimAccounts(ContentResolver)}.
+                 */
+                public static final int DEFAULT_ACCOUNT_STATE_SIM = 4;
+
+                /**
                  * The state of the default account. One of
                  * {@link #DEFAULT_ACCOUNT_STATE_NOT_SET},
-                 * {@link #DEFAULT_ACCOUNT_STATE_LOCAL} or
-                 * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}.
+                 * {@link #DEFAULT_ACCOUNT_STATE_LOCAL},
+                 * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}
+                 * {@link #DEFAULT_ACCOUNT_STATE_SIM}.
                  */
                 @DefaultAccountState
                 private final int mState;
 
                 /**
-                 * The account of the default account, when {@link mState} is {
+                 * The account of the default account, when {@link #mState} is {
                  *
-                 * @link #STATE_SET_TO_CLOUD}, or null otherwise.
+                 * @link #DEFAULT_ACCOUNT_STATE_CLOUD} or {@link #DEFAULT_ACCOUNT_STATE_SIM}, or
+                 * null otherwise.
                  */
-                private final Account mCloudAccount;
+                private final Account mAccount;
 
                 /**
                  * Constructs a new `DefaultAccountAndState` instance with the specified state and
                  * cloud
                  * account.
                  *
-                 * @param state        The state of the default account.
-                 * @param cloudAccount The cloud account associated with the default account,
-                 *                     or null if the state is not
-                 *                     {@link #DEFAULT_ACCOUNT_STATE_CLOUD}.
+                 * @param state   The state of the default account.
+                 * @param account The account associated with the default account if the state is
+                 *                {@link #DEFAULT_ACCOUNT_STATE_CLOUD} or
+                 *                {@link #DEFAULT_ACCOUNT_STATE_SIM}, or null otherwise.
                  */
                 public DefaultAccountAndState(@DefaultAccountState int state,
-                        @Nullable Account cloudAccount) {
+                        @Nullable Account account) {
                     if (!isValidDefaultAccountState(state)) {
                         throw new IllegalArgumentException("Invalid default account state.");
                     }
-                    if ((state == DEFAULT_ACCOUNT_STATE_CLOUD) != (cloudAccount != null)) {
+                    if (isCloudOrSimAccount(state) != (account != null)) {
                         throw new IllegalArgumentException(
-                                "Default account can be set to cloud if and only if the cloud "
+                                "Default account can be set to cloud or SIM if and only if the "
                                         + "account is provided.");
                     }
                     this.mState = state;
-                    this.mCloudAccount =
-                            (mState == DEFAULT_ACCOUNT_STATE_CLOUD) ? cloudAccount : null;
+                    this.mAccount = isCloudOrSimAccount(state) ? account : null;
                 }
 
                 /**
@@ -3118,6 +3130,21 @@
                     return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_CLOUD, cloudAccount);
                 }
 
+
+                /**
+                 * Creates a `DefaultAccountAndState` instance representing a default account
+                 * that is set to the sim and associated with the specified sim account.
+                 *
+                 * @param simAccount The non-null sim account associated with the default
+                 *                   contacts account.
+                 * @return A new `DefaultAccountAndState` instance with state
+                 * {@link #DEFAULT_ACCOUNT_STATE_SIM}.
+                 */
+                public static @NonNull DefaultAccountAndState ofSim(
+                        @NonNull Account simAccount) {
+                    return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_SIM, simAccount);
+                }
+
                 /**
                  * Creates a `DefaultAccountAndState` instance representing a default account
                  * that is set to the local device storage.
@@ -3140,6 +3167,18 @@
                     return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_NOT_SET, null);
                 }
 
+                private static boolean isCloudOrSimAccount(@DefaultAccountState int state) {
+                    return state == DEFAULT_ACCOUNT_STATE_CLOUD
+                            || state == DEFAULT_ACCOUNT_STATE_SIM;
+                }
+
+                private static boolean isValidDefaultAccountState(int state) {
+                    return state == DEFAULT_ACCOUNT_STATE_NOT_SET
+                            || state == DEFAULT_ACCOUNT_STATE_LOCAL
+                            || state == DEFAULT_ACCOUNT_STATE_CLOUD
+                            || state == DEFAULT_ACCOUNT_STATE_SIM;
+                }
+
                 /**
                  * @return the state of the default account.
                  */
@@ -3149,16 +3188,17 @@
                 }
 
                 /**
-                 * @return the cloud account associated with the default account, or null if the
-                 * state is not {@link #DEFAULT_ACCOUNT_STATE_CLOUD}.
+                 * @return the cloud account associated with the default account if the
+                 * state is {@link #DEFAULT_ACCOUNT_STATE_CLOUD} or
+                 * {@link #DEFAULT_ACCOUNT_STATE_SIM}.
                  */
-                public @Nullable Account getCloudAccount() {
-                    return mCloudAccount;
+                public @Nullable Account getAccount() {
+                    return mAccount;
                 }
 
                 @Override
                 public int hashCode() {
-                    return Objects.hash(mState, mCloudAccount);
+                    return Objects.hash(mState, mAccount);
                 }
 
                 @Override
@@ -3170,14 +3210,8 @@
                         return false;
                     }
 
-                    return mState == that.mState && Objects.equals(mCloudAccount,
-                            that.mCloudAccount);
-                }
-
-                private static boolean isValidDefaultAccountState(int state) {
-                    return state == DEFAULT_ACCOUNT_STATE_NOT_SET
-                            || state == DEFAULT_ACCOUNT_STATE_LOCAL
-                            || state == DEFAULT_ACCOUNT_STATE_CLOUD;
+                    return mState == that.mState && Objects.equals(mAccount,
+                            that.mAccount);
                 }
 
                 /**
@@ -3189,7 +3223,8 @@
                 @IntDef(
                         prefix = {"DEFAULT_ACCOUNT_STATE_"},
                         value = {DEFAULT_ACCOUNT_STATE_NOT_SET,
-                                DEFAULT_ACCOUNT_STATE_LOCAL, DEFAULT_ACCOUNT_STATE_CLOUD})
+                                DEFAULT_ACCOUNT_STATE_LOCAL, DEFAULT_ACCOUNT_STATE_CLOUD,
+                                DEFAULT_ACCOUNT_STATE_SIM})
                 public @interface DefaultAccountState {
                 }
             }
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index d45b24e..303197d 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -202,10 +202,8 @@
     private static final int DEFAULT_CALLS_SOURCE = SOURCE_STAR;
 
     public static final String MANUAL_RULE_ID = "MANUAL_RULE";
-    public static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE";
+    public static final String EVENTS_OBSOLETE_RULE_ID = "EVENTS_DEFAULT_RULE";
     public static final String EVERY_NIGHT_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE";
-    public static final List<String> DEFAULT_RULE_IDS = Arrays.asList(EVERY_NIGHT_DEFAULT_RULE_ID,
-            EVENTS_DEFAULT_RULE_ID);
 
     public static final int[] ALL_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY,
             Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY };
@@ -424,21 +422,10 @@
         return policy;
     }
 
+    @FlaggedApi(Flags.FLAG_MODES_UI)
     public static ZenModeConfig getDefaultConfig() {
         ZenModeConfig config = new ZenModeConfig();
 
-        EventInfo eventInfo = new EventInfo();
-        eventInfo.reply = REPLY_YES_OR_MAYBE;
-        ZenRule events = new ZenRule();
-        events.id = EVENTS_DEFAULT_RULE_ID;
-        events.conditionId = toEventConditionId(eventInfo);
-        events.component = ComponentName.unflattenFromString(
-                "android/com.android.server.notification.EventConditionProvider");
-        events.enabled = false;
-        events.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-        events.pkg = "android";
-        config.automaticRules.put(EVENTS_DEFAULT_RULE_ID, events);
-
         ScheduleInfo scheduleInfo = new ScheduleInfo();
         scheduleInfo.days = new int[] {1, 2, 3, 4, 5, 6, 7};
         scheduleInfo.startHour = 22;
@@ -457,6 +444,13 @@
         return config;
     }
 
+    // TODO: b/368247671 - Can be made a constant again when modes_ui is inlined
+    public static List<String> getDefaultRuleIds() {
+        return Flags.modesUi()
+            ? List.of(EVERY_NIGHT_DEFAULT_RULE_ID)
+            : List.of(EVERY_NIGHT_DEFAULT_RULE_ID, EVENTS_OBSOLETE_RULE_ID);
+    }
+
     void ensureManualZenRule() {
         if (manualRule == null) {
             final ZenRule newRule = new ZenRule();
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 384add5..2ab16e9 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -2397,7 +2397,11 @@
                 // it hasn't changed and there is no need to update.
                 ret = mBlastBufferQueue.createSurface();
             } else {
-                mBlastBufferQueue.update(mBbqSurfaceControl, width, height, format);
+                if (mBbqSurfaceControl != null && mBbqSurfaceControl.isValid()) {
+                    mBlastBufferQueue.update(mBbqSurfaceControl, width, height, format);
+                } else {
+                    Log.w(TAG, "Skipping BlastBufferQueue update - invalid surface control");
+                }
             }
 
             return ret;
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index e90b1c0..229e8ee7 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -26,7 +26,6 @@
 import android.os.IBinder;
 import android.os.Trace;
 import android.util.proto.ProtoOutputStream;
-import android.view.SurfaceControl.Transaction;
 import android.view.inputmethod.Flags;
 import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethodManager;
@@ -34,8 +33,6 @@
 import com.android.internal.inputmethod.ImeTracing;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 
-import java.util.function.Supplier;
-
 /**
  * Controls the visibility and animations of IME window insets source.
  * @hide
@@ -54,10 +51,8 @@
      */
     private boolean mIsRequestedVisibleAwaitingLeash;
 
-    public ImeInsetsSourceConsumer(
-            int id, InsetsState state, Supplier<Transaction> transactionSupplier,
-            InsetsController controller) {
-        super(id, WindowInsets.Type.ime(), state, transactionSupplier, controller);
+    public ImeInsetsSourceConsumer(int id, InsetsState state, InsetsController controller) {
+        super(id, WindowInsets.Type.ime(), state, controller);
     }
 
     @Override
diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java
index 04bb609..a0d8a17 100644
--- a/core/java/android/view/InsetsAnimationControlCallbacks.java
+++ b/core/java/android/view/InsetsAnimationControlCallbacks.java
@@ -54,13 +54,6 @@
     void notifyFinished(InsetsAnimationControlRunner runner, boolean shown);
 
     /**
-     * Apply the new params to the surface.
-     * @param params The {@link android.view.SyncRtSurfaceTransactionApplier.SurfaceParams} to
-     *               apply.
-     */
-    void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params);
-
-    /**
      * Post a message to release the Surface, guaranteed to happen after all
      * previous calls to applySurfaceParams.
      */
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 91e9230..97facc1 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -99,6 +99,7 @@
     private final @InsetsType int mTypes;
     private @InsetsType int mControllingTypes;
     private final InsetsAnimationControlCallbacks mController;
+    private final SurfaceParamsApplier mSurfaceParamsApplier;
     private final WindowInsetsAnimation mAnimation;
     private final long mDurationMs;
     private final Interpolator mInterpolator;
@@ -123,6 +124,7 @@
     public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls,
             @Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener,
             @InsetsType int types, InsetsAnimationControlCallbacks controller,
+            SurfaceParamsApplier surfaceParamsApplier,
             InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
             @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
             CompatibilityInfo.Translator translator, @Nullable ImeTracker.Token statsToken) {
@@ -131,6 +133,7 @@
         mTypes = types;
         mControllingTypes = types;
         mController = controller;
+        mSurfaceParamsApplier = surfaceParamsApplier;
         mInitialInsetsState = new InsetsState(state, true /* copySources */);
         if (frame != null) {
             final SparseIntArray idSideMap = new SparseIntArray();
@@ -258,6 +261,11 @@
     }
 
     @Override
+    public SurfaceParamsApplier getSurfaceParamsApplier() {
+        return mSurfaceParamsApplier;
+    }
+
+    @Override
     @Nullable
     public ImeTracker.Token getStatsToken() {
         return mStatsToken;
@@ -305,7 +313,7 @@
         updateLeashesForSide(SIDE_RIGHT, offset.right, params, outState, mPendingAlpha);
         updateLeashesForSide(SIDE_BOTTOM, offset.bottom, params, outState, mPendingAlpha);
 
-        mController.applySurfaceParams(params.toArray(new SurfaceParams[params.size()]));
+        mSurfaceParamsApplier.applySurfaceParams(params.toArray(new SurfaceParams[params.size()]));
         mCurrentInsets = mPendingInsets;
         mAnimation.setFraction(mPendingFraction);
         mCurrentAlpha = mPendingAlpha;
diff --git a/core/java/android/view/InsetsAnimationControlRunner.java b/core/java/android/view/InsetsAnimationControlRunner.java
index 8cb8b47..4f102da 100644
--- a/core/java/android/view/InsetsAnimationControlRunner.java
+++ b/core/java/android/view/InsetsAnimationControlRunner.java
@@ -77,6 +77,11 @@
     @AnimationType int getAnimationType();
 
     /**
+     * @return The {@link SurfaceParamsApplier} this runner is using.
+     */
+    SurfaceParamsApplier getSurfaceParamsApplier();
+
+    /**
      * @return The token tracking the current IME request or {@code null} otherwise.
      */
     @Nullable
@@ -99,4 +104,27 @@
      * @param fieldId FieldId of the implementation class
      */
     void dumpDebug(ProtoOutputStream proto, long fieldId);
+
+    /**
+     * Interface applying given surface operations.
+     */
+    interface SurfaceParamsApplier {
+
+        SurfaceParamsApplier DEFAULT = params -> {
+            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            for (int i = params.length - 1; i >= 0; i--) {
+                SyncRtSurfaceTransactionApplier.applyParams(t, params[i], new float[9]);
+            }
+            t.apply();
+            t.close();
+        };
+
+        /**
+         * Apply the new params to the surface.
+         *
+         * @param params The {@link SyncRtSurfaceTransactionApplier.SurfaceParams} to apply.
+         */
+        void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params);
+
+    }
 }
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index fc185bc..8c2c495 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -17,7 +17,6 @@
 package android.view;
 
 import static android.view.InsetsController.DEBUG;
-import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
 
 import android.annotation.Nullable;
 import android.annotation.UiThread;
@@ -30,7 +29,6 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.InsetsController.AnimationType;
 import android.view.InsetsController.LayoutInsetsDuringAnimation;
-import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimation.Bounds;
 import android.view.inputmethod.ImeTracker;
@@ -50,8 +48,6 @@
     private final InsetsAnimationControlCallbacks mCallbacks =
             new InsetsAnimationControlCallbacks() {
 
-        private final float[] mTmpFloat9 = new float[9];
-
         @Override
         @UiThread
         public <T extends InsetsAnimationControlRunner & InternalInsetsAnimationController>
@@ -81,19 +77,6 @@
         }
 
         @Override
-        public void applySurfaceParams(SurfaceParams... params) {
-            if (DEBUG) Log.d(TAG, "applySurfaceParams");
-            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-            for (int i = params.length - 1; i >= 0; i--) {
-                SyncRtSurfaceTransactionApplier.SurfaceParams surfaceParams = params[i];
-                applyParams(t, surfaceParams, mTmpFloat9);
-            }
-            t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
-            t.apply();
-            t.close();
-        }
-
-        @Override
         public void releaseSurfaceControlFromRt(SurfaceControl sc) {
             if (DEBUG) Log.d(TAG, "releaseSurfaceControlFromRt");
             // Since we don't push the SurfaceParams to the RT we can release directly
@@ -106,6 +89,22 @@
         }
     };
 
+    private SurfaceParamsApplier mSurfaceParamsApplier = new SurfaceParamsApplier() {
+
+        private final float[] mTmpFloat9 = new float[9];
+
+        @Override
+        public void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params) {
+            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            for (int i = params.length - 1; i >= 0; i--) {
+                SyncRtSurfaceTransactionApplier.applyParams(t, params[i], mTmpFloat9);
+            }
+            t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
+            t.apply();
+            t.close();
+        }
+    };
+
     @UiThread
     public InsetsAnimationThreadControlRunner(SparseArray<InsetsSourceControl> controls,
             @Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener,
@@ -117,8 +116,8 @@
         mMainThreadHandler = mainThreadHandler;
         mOuterCallbacks = controller;
         mControl = new InsetsAnimationControlImpl(controls, frame, state, listener, types,
-                mCallbacks, insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
-                translator, statsToken);
+                mCallbacks, mSurfaceParamsApplier, insetsAnimationSpec, animationType,
+                layoutInsetsDuringAnimation, translator, statsToken);
         InsetsAnimationThread.getHandler().post(() -> {
             if (mControl.isCancelled()) {
                 return;
@@ -187,6 +186,11 @@
     }
 
     @Override
+    public SurfaceParamsApplier getSurfaceParamsApplier() {
+        return mSurfaceParamsApplier;
+    }
+
+    @Override
     public void updateLayoutInsetsDuringAnimation(
             @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) {
         InsetsAnimationThread.getHandler().post(
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 8fdf91a..e38281f 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -55,7 +55,6 @@
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.InsetsSourceConsumer.ShowResult;
-import android.view.SurfaceControl.Transaction;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimation.Bounds;
@@ -85,7 +84,8 @@
  * Implements {@link WindowInsetsController} on the client.
  * @hide
  */
-public class InsetsController implements WindowInsetsController, InsetsAnimationControlCallbacks {
+public class InsetsController implements WindowInsetsController, InsetsAnimationControlCallbacks,
+        InsetsAnimationControlRunner.SurfaceParamsApplier {
 
     private int mTypesBeingCancelled;
 
@@ -307,7 +307,6 @@
     }
 
     /** Not running an animation. */
-    @VisibleForTesting
     public static final int ANIMATION_TYPE_NONE = -1;
 
     /** Running animation will show insets */
@@ -317,11 +316,9 @@
     public static final int ANIMATION_TYPE_HIDE = 1;
 
     /** Running animation is controlled by user via {@link #controlWindowInsetsAnimation} */
-    @VisibleForTesting(visibility = PACKAGE)
     public static final int ANIMATION_TYPE_USER = 2;
 
     /** Running animation will resize insets */
-    @VisibleForTesting
     public static final int ANIMATION_TYPE_RESIZE = 3;
 
     @Retention(RetentionPolicy.SOURCE)
@@ -757,11 +754,9 @@
     public InsetsController(Host host) {
         this(host, (controller, id, type) -> {
             if (!Flags.refactorInsetsController() &&  type == ime()) {
-                return new ImeInsetsSourceConsumer(id, controller.mState,
-                        Transaction::new, controller);
+                return new ImeInsetsSourceConsumer(id, controller.mState, controller);
             } else {
-                return new InsetsSourceConsumer(id, type, controller.mState,
-                        Transaction::new, controller);
+                return new InsetsSourceConsumer(id, type, controller.mState, controller);
             }
         }, host.getHandler());
     }
@@ -1525,9 +1520,15 @@
                         insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
                         mHost.getTranslator(), mHost.getHandler(), statsToken)
                 : new InsetsAnimationControlImpl(controls,
-                        frame, mState, listener, typesReady, this, insetsAnimationSpec,
+                        frame, mState, listener, typesReady, this, this, insetsAnimationSpec,
                         animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
                         statsToken);
+        for (int i = controls.size() - 1; i >= 0; i--) {
+            final InsetsSourceConsumer consumer = mSourceConsumers.get(controls.keyAt(i));
+            if (consumer != null) {
+                consumer.setSurfaceParamsApplier(runner.getSurfaceParamsApplier());
+            }
+        }
         if ((typesReady & WindowInsets.Type.ime()) != 0) {
             ImeTracing.getInstance().triggerClientDump("InsetsAnimationControlImpl",
                     mHost.getInputMethodManager(), null /* icProto */);
diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java
index f90b841..5262751 100644
--- a/core/java/android/view/InsetsResizeAnimationRunner.java
+++ b/core/java/android/view/InsetsResizeAnimationRunner.java
@@ -94,6 +94,11 @@
     }
 
     @Override
+    public SurfaceParamsApplier getSurfaceParamsApplier() {
+        return SurfaceParamsApplier.DEFAULT;
+    }
+
+    @Override
     @Nullable
     public ImeTracker.Token getStatsToken() {
         // Return null as resizing the IME view is not explicitly tracked.
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 391d757..2e2ff1d 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import static android.view.InsetsController.ANIMATION_TYPE_NONE;
+import static android.view.InsetsController.ANIMATION_TYPE_RESIZE;
 import static android.view.InsetsController.AnimationType;
 import static android.view.InsetsController.DEBUG;
 import static android.view.InsetsSourceConsumerProto.ANIMATION_STATE;
@@ -32,12 +33,13 @@
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
+import android.graphics.Matrix;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
-import android.view.SurfaceControl.Transaction;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.inputmethod.Flags;
 import android.view.inputmethod.ImeTracker;
@@ -48,7 +50,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
-import java.util.function.Supplier;
 
 /**
  * Controls the visibility and animations of a single window insets source.
@@ -92,10 +93,12 @@
     private final int mType;
 
     private static final String TAG = "InsetsSourceConsumer";
-    private final Supplier<Transaction> mTransactionSupplier;
     @Nullable
     private InsetsSourceControl mSourceControl;
     private boolean mHasWindowFocus;
+    private InsetsAnimationControlRunner.SurfaceParamsApplier mSurfaceParamsApplier =
+            InsetsAnimationControlRunner.SurfaceParamsApplier.DEFAULT;
+    private final Matrix mTmpMatrix = new Matrix();
 
     /**
      * Whether the view has focus returned by {@link #onWindowFocusGained(boolean)}.
@@ -108,16 +111,13 @@
      * @param id The ID of the consumed insets.
      * @param type The {@link InsetsType} of the consumed insets.
      * @param state The current {@link InsetsState} of the consumed insets.
-     * @param transactionSupplier The source of new {@link Transaction} instances. The supplier
-     *         must provide *new* instances, which will be explicitly closed by this class.
      * @param controller The {@link InsetsController} to use for insets interaction.
      */
     public InsetsSourceConsumer(int id, @InsetsType int type, InsetsState state,
-            Supplier<Transaction> transactionSupplier, InsetsController controller) {
+            InsetsController controller) {
         mId = id;
         mType = type;
         mState = state;
-        mTransactionSupplier = transactionSupplier;
         mController = controller;
     }
 
@@ -162,6 +162,9 @@
             if (localVisible != serverVisible) {
                 mController.notifyVisibilityChanged();
             }
+
+            // Reset the applier to the default one which has the most lightweight implementation.
+            setSurfaceParamsApplier(InsetsAnimationControlRunner.SurfaceParamsApplier.DEFAULT);
         } else {
             final boolean requestedVisible = isRequestedVisibleAwaitingControl();
             final SurfaceControl oldLeash = lastControl != null ? lastControl.getLeash() : null;
@@ -184,10 +187,11 @@
                     mController.notifyVisibilityChanged();
                 }
 
-                // If we have a new leash, make sure visibility is up-to-date, even though we
-                // didn't want to run an animation above.
-                if (mController.getAnimationType(mType) == ANIMATION_TYPE_NONE) {
-                    applyRequestedVisibilityToControl();
+                // If there is no animation controlling the leash, make sure the visibility and the
+                // position is up-to-date. Note: ANIMATION_TYPE_RESIZE doesn't control the leash.
+                final int animType = mController.getAnimationType(mType);
+                if (animType == ANIMATION_TYPE_NONE || animType == ANIMATION_TYPE_RESIZE) {
+                    applyRequestedVisibilityAndPositionToControl();
                 }
 
                 // Remove the surface that owned by last control when it lost.
@@ -229,6 +233,15 @@
     }
 
     /**
+     * Sets the SurfaceParamsApplier that the latest animation runner is using. The leash owned by
+     * this class is always applied by the applier, so that the transaction order can always be
+     * aligned with the calling sequence.
+     */
+    void setSurfaceParamsApplier(InsetsAnimationControlRunner.SurfaceParamsApplier applier) {
+        mSurfaceParamsApplier = applier;
+    }
+
+    /**
      * Called right after the animation is started or finished.
      */
     @VisibleForTesting(visibility = PACKAGE)
@@ -431,24 +444,30 @@
         if (DEBUG) Log.d(TAG, "updateSource: " + newSource);
     }
 
-    private void applyRequestedVisibilityToControl() {
-        if (mSourceControl == null || mSourceControl.getLeash() == null) {
+    private void applyRequestedVisibilityAndPositionToControl() {
+        if (mSourceControl == null) {
+            return;
+        }
+        final SurfaceControl leash = mSourceControl.getLeash();
+        if (leash == null) {
             return;
         }
 
-        final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0;
-        try (Transaction t = mTransactionSupplier.get()) {
-            if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + requestedVisible);
-            if (requestedVisible) {
-                t.show(mSourceControl.getLeash());
-            } else {
-                t.hide(mSourceControl.getLeash());
-            }
-            // Ensure the alpha value is aligned with the actual requested visibility.
-            t.setAlpha(mSourceControl.getLeash(), requestedVisible ? 1 : 0);
-            t.apply();
-        }
-        onPerceptible(requestedVisible);
+        final boolean visible = (mController.getRequestedVisibleTypes() & mType) != 0;
+        final Point surfacePosition = mSourceControl.getSurfacePosition();
+
+        if (DEBUG) Log.d(TAG, "applyRequestedVisibilityAndPositionToControl: visible=" + visible
+                + " position=" + surfacePosition);
+
+        mTmpMatrix.setTranslate(surfacePosition.x, surfacePosition.y);
+        mSurfaceParamsApplier.applySurfaceParams(
+                new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(leash)
+                        .withVisibility(visible)
+                        .withAlpha(visible ? 1 : 0)
+                        .withMatrix(mTmpMatrix)
+                        .build());
+
+        onPerceptible(visible);
     }
 
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/core/java/android/window/flags/DesktopModeFlags.java b/core/java/android/window/flags/DesktopModeFlags.java
index 395a853..944a106 100644
--- a/core/java/android/window/flags/DesktopModeFlags.java
+++ b/core/java/android/window/flags/DesktopModeFlags.java
@@ -43,7 +43,7 @@
     // All desktop mode related flags to be overridden by developer option toggle will be added here
     ENABLE_DESKTOP_WINDOWING_MODE(
             Flags::enableDesktopWindowingMode, /* shouldOverrideByDevOption= */ true),
-    ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, false),
+    ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true),
     ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION(
             Flags::enableCaptionCompatInsetForceConsumption, true),
     ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS(
@@ -58,12 +58,13 @@
     ENABLE_TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true),
     ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS(Flags::enableDesktopWindowingSizeConstraints, true),
     DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE(Flags::disableNonResizableAppSnapResizing, true),
-    ENABLE_WINDOWING_SCALED_RESIZING(Flags::enableWindowingScaledResizing, false),
+    ENABLE_WINDOWING_SCALED_RESIZING(Flags::enableWindowingScaledResizing, true),
     ENABLE_DESKTOP_WINDOWING_TASK_LIMIT(Flags::enableDesktopWindowingTaskLimit, true),
     ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION(Flags::enableDesktopWindowingBackNavigation, true),
     ENABLE_WINDOWING_EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true),
     ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS(
-            Flags::enableDesktopWindowingTaskbarRunningApps, true);
+            Flags::enableDesktopWindowingTaskbarRunningApps, true),
+    ENABLE_DESKTOP_WINDOWING_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false);
 
     private static final String TAG = "DesktopModeFlagsUtil";
     // Function called to obtain aconfig flag value.
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index e1402f8..b6aad11 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -266,3 +266,14 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "remove_starting_window_wait_for_multi_transitions"
+  namespace: "windowing_frontend"
+  description: "Avoid remove starting window too early when playing multiple transitions"
+  bug: "362347290"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/com/android/internal/os/anr/OWNERS b/core/java/com/android/internal/os/anr/OWNERS
index 9816752..1ad642f 100644
--- a/core/java/com/android/internal/os/anr/OWNERS
+++ b/core/java/com/android/internal/os/anr/OWNERS
@@ -1,3 +1,2 @@
 benmiles@google.com
-gaillard@google.com
 mohamadmahmoud@google.com
\ No newline at end of file
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index fbc058c..b0e38e2 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -122,18 +122,20 @@
     private final Lock mBackgroundServiceLock = new ReentrantLock();
     private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
 
-    public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups) {
+    public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups)
+            throws ServiceManager.ServiceNotFoundException {
         this(null, null, null, () -> {}, groups);
     }
 
-    public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups) {
+    public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups)
+            throws ServiceManager.ServiceNotFoundException {
         this(null, null, null, cacheUpdater, groups);
     }
 
     public PerfettoProtoLogImpl(
             @NonNull String viewerConfigFilePath,
             @NonNull Runnable cacheUpdater,
-            @NonNull IProtoLogGroup[] groups) {
+            @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
         this(viewerConfigFilePath,
                 null,
                 new ProtoLogViewerConfigReader(() -> {
@@ -177,12 +179,14 @@
             @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
             @Nullable ProtoLogViewerConfigReader viewerConfigReader,
             @NonNull Runnable cacheUpdater,
-            @NonNull IProtoLogGroup[] groups) {
+            @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
         this(viewerConfigFilePath, viewerConfigInputStreamProvider, viewerConfigReader,
                 cacheUpdater, groups,
                 ProtoLogDataSource::new,
-                IProtoLogConfigurationService.Stub
-                        .asInterface(ServiceManager.getService(PROTOLOG_CONFIGURATION_SERVICE))
+                android.tracing.Flags.clientSideProtoLogging() ?
+                    IProtoLogConfigurationService.Stub.asInterface(
+                        ServiceManager.getServiceOrThrow(PROTOLOG_CONFIGURATION_SERVICE)
+                    ) : null
         );
     }
 
@@ -222,7 +226,7 @@
         if (android.tracing.Flags.clientSideProtoLogging()) {
             mProtoLogConfigurationService = configurationService;
             Objects.requireNonNull(mProtoLogConfigurationService,
-                    "ServiceManager returned a null ProtoLog Configuration Service");
+                    "A null ProtoLog Configuration Service was provided!");
 
             try {
                 var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs();
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index bf77db7..adf03fe 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.protolog;
 
+import android.os.ServiceManager;
+
 import com.android.internal.protolog.common.IProtoLog;
 import com.android.internal.protolog.common.IProtoLogGroup;
 import com.android.internal.protolog.common.LogLevel;
@@ -76,7 +78,11 @@
                     groups = allGroups.toArray(new IProtoLogGroup[0]);
                 }
 
-                sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+                try {
+                    sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+                } catch (ServiceManager.ServiceNotFoundException e) {
+                    throw new RuntimeException(e);
+                }
             }
         } else {
             sProtoLogInstance = new LogcatOnlyProtoLogImpl();
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 7bdcf2d..5d67534 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -23,6 +23,7 @@
 import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.VIEWER_CONFIG_PATH;
 
 import android.annotation.Nullable;
+import android.os.ServiceManager;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -106,18 +107,23 @@
             final var groups = sLogGroups.values().toArray(new IProtoLogGroup[0]);
 
             if (android.tracing.Flags.perfettoProtologTracing()) {
-                File f = new File(sViewerConfigPath);
-                if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) {
-                    // TODO(b/353530422): Remove - temporary fix to unblock b/352290057
-                    // In some tests the viewer config file might not exist in which we don't
-                    // want to provide config path to the user
-                    Log.w(LOG_TAG, "Failed to find viewerConfigFile when setting up "
-                            + ProtoLogImpl.class.getSimpleName() + ". "
-                            + "Setting up without a viewer config instead...");
-                    sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups);
-                } else {
-                    sServiceInstance =
-                            new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups);
+                try {
+                    File f = new File(sViewerConfigPath);
+                    if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) {
+                        // TODO(b/353530422): Remove - temporary fix to unblock b/352290057
+                        // In some tests the viewer config file might not exist in which we don't
+                        // want to provide config path to the user
+                        Log.w(LOG_TAG, "Failed to find viewerConfigFile when setting up "
+                                + ProtoLogImpl.class.getSimpleName() + ". "
+                                + "Setting up without a viewer config instead...");
+
+                        sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups);
+                    } else {
+                        sServiceInstance =
+                                new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups);
+                    }
+                } catch (ServiceManager.ServiceNotFoundException e) {
+                    throw new RuntimeException(e);
                 }
             } else {
                 var protologImpl = new LegacyProtoLogImpl(
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index e65b4b6..c0a7383 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -16,14 +16,19 @@
 
 package com.android.internal.widget;
 
+import static java.lang.Float.NaN;
+
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Insets;
 import android.graphics.Paint;
 import android.graphics.Paint.FontMetricsInt;
 import android.graphics.Path;
+import android.graphics.PorterDuff;
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.hardware.input.InputManager;
@@ -65,11 +70,14 @@
     private static final PointerState EMPTY_POINTER_STATE = new PointerState();
 
     public static class PointerState {
-        // Trace of previous points.
-        private float[] mTraceX = new float[32];
-        private float[] mTraceY = new float[32];
-        private boolean[] mTraceCurrent = new boolean[32];
-        private int mTraceCount;
+        private float mCurrentX = NaN;
+        private float mCurrentY = NaN;
+        private float mPreviousX = NaN;
+        private float mPreviousY = NaN;
+        private float mFirstX = NaN;
+        private float mFirstY = NaN;
+        private boolean mPreviousPointIsHistorical;
+        private boolean mCurrentPointIsHistorical;
 
         // True if the pointer is down.
         @UnsupportedAppUsage
@@ -96,31 +104,20 @@
         public PointerState() {
         }
 
-        public void clearTrace() {
-            mTraceCount = 0;
-        }
-
-        public void addTrace(float x, float y, boolean current) {
-            int traceCapacity = mTraceX.length;
-            if (mTraceCount == traceCapacity) {
-                traceCapacity *= 2;
-                float[] newTraceX = new float[traceCapacity];
-                System.arraycopy(mTraceX, 0, newTraceX, 0, mTraceCount);
-                mTraceX = newTraceX;
-
-                float[] newTraceY = new float[traceCapacity];
-                System.arraycopy(mTraceY, 0, newTraceY, 0, mTraceCount);
-                mTraceY = newTraceY;
-
-                boolean[] newTraceCurrent = new boolean[traceCapacity];
-                System.arraycopy(mTraceCurrent, 0, newTraceCurrent, 0, mTraceCount);
-                mTraceCurrent= newTraceCurrent;
+        public void addTrace(float x, float y, boolean isHistorical) {
+            if (Float.isNaN(mFirstX)) {
+                mFirstX = x;
+            }
+            if (Float.isNaN(mFirstY)) {
+                mFirstY = y;
             }
 
-            mTraceX[mTraceCount] = x;
-            mTraceY[mTraceCount] = y;
-            mTraceCurrent[mTraceCount] = current;
-            mTraceCount += 1;
+            mPreviousX = mCurrentX;
+            mPreviousY = mCurrentY;
+            mCurrentX = x;
+            mCurrentY = y;
+            mPreviousPointIsHistorical = mCurrentPointIsHistorical;
+            mCurrentPointIsHistorical = isHistorical;
         }
     }
 
@@ -149,6 +146,12 @@
     private final SparseArray<PointerState> mPointers = new SparseArray<PointerState>();
     private final PointerCoords mTempCoords = new PointerCoords();
 
+    // Draw the trace of all pointers in the current gesture in a separate layer
+    // that is not cleared on every frame so that we don't have to re-draw the
+    // entire trace on each frame.
+    private final Bitmap mTraceBitmap;
+    private final Canvas mTraceCanvas;
+
     private final Region mSystemGestureExclusion = new Region();
     private final Region mSystemGestureExclusionRejected = new Region();
     private final Path mSystemGestureExclusionPath = new Path();
@@ -197,6 +200,10 @@
         mPathPaint.setARGB(255, 0, 96, 255);
         mPathPaint.setStyle(Paint.Style.STROKE);
 
+        mTraceBitmap = Bitmap.createBitmap(getResources().getDisplayMetrics().widthPixels,
+                getResources().getDisplayMetrics().heightPixels, Bitmap.Config.ARGB_8888);
+        mTraceCanvas = new Canvas(mTraceBitmap);
+
         configureDensityDependentFactors();
 
         mSystemGestureExclusionPaint = new Paint();
@@ -256,7 +263,7 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         mTextPaint.getFontMetricsInt(mTextMetrics);
-        mHeaderBottom = mHeaderPaddingTop-mTextMetrics.ascent+mTextMetrics.descent+2;
+        mHeaderBottom = mHeaderPaddingTop - mTextMetrics.ascent + mTextMetrics.descent + 2;
         if (false) {
             Log.i("foo", "Metrics: ascent=" + mTextMetrics.ascent
                     + " descent=" + mTextMetrics.descent
@@ -269,6 +276,7 @@
     // Draw an oval.  When angle is 0 radians, orients the major axis vertically,
     // angles less than or greater than 0 radians rotate the major axis left or right.
     private RectF mReusableOvalRect = new RectF();
+
     private void drawOval(Canvas canvas, float x, float y, float major, float minor,
             float angle, Paint paint) {
         canvas.save(Canvas.MATRIX_SAVE_FLAG);
@@ -285,6 +293,8 @@
     protected void onDraw(Canvas canvas) {
         final int NP = mPointers.size();
 
+        canvas.drawBitmap(mTraceBitmap, 0, 0, null);
+
         if (!mSystemGestureExclusion.isEmpty()) {
             mSystemGestureExclusionPath.reset();
             mSystemGestureExclusion.getBoundaryPath(mSystemGestureExclusionPath);
@@ -303,32 +313,9 @@
         // Pointer trace.
         for (int p = 0; p < NP; p++) {
             final PointerState ps = mPointers.valueAt(p);
+            float lastX = ps.mCurrentX, lastY = ps.mCurrentY;
 
-            // Draw path.
-            final int N = ps.mTraceCount;
-            float lastX = 0, lastY = 0;
-            boolean haveLast = false;
-            boolean drawn = false;
-            mPaint.setARGB(255, 128, 255, 255);
-            for (int i=0; i < N; i++) {
-                float x = ps.mTraceX[i];
-                float y = ps.mTraceY[i];
-                if (Float.isNaN(x) || Float.isNaN(y)) {
-                    haveLast = false;
-                    continue;
-                }
-                if (haveLast) {
-                    canvas.drawLine(lastX, lastY, x, y, mPathPaint);
-                    final Paint paint = ps.mTraceCurrent[i - 1] ? mCurrentPointPaint : mPaint;
-                    canvas.drawPoint(lastX, lastY, paint);
-                    drawn = true;
-                }
-                lastX = x;
-                lastY = y;
-                haveLast = true;
-            }
-
-            if (drawn) {
+            if (!Float.isNaN(lastX) && !Float.isNaN(lastY)) {
                 // Draw velocity vector.
                 mPaint.setARGB(255, 255, 64, 128);
                 float xVel = ps.mXVelocity * (1000 / 60);
@@ -353,7 +340,7 @@
                         Math.max(getHeight(), getWidth()), mTargetPaint);
 
                 // Draw current point.
-                int pressureLevel = (int)(ps.mCoords.pressure * 255);
+                int pressureLevel = (int) (ps.mCoords.pressure * 255);
                 mPaint.setARGB(255, pressureLevel, 255, 255 - pressureLevel);
                 canvas.drawPoint(ps.mCoords.x, ps.mCoords.y, mPaint);
 
@@ -424,8 +411,7 @@
                 .append(" / ").append(mMaxNumPointers)
                 .toString(), 1, base, mTextPaint);
 
-        final int count = ps.mTraceCount;
-        if ((mCurDown && ps.mCurDown) || count == 0) {
+        if ((mCurDown && ps.mCurDown) || Float.isNaN(ps.mCurrentX)) {
             canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom,
                     mTextBackgroundPaint);
             canvas.drawText(mText.clear()
@@ -437,8 +423,8 @@
                     .append("Y: ").append(ps.mCoords.y, 1)
                     .toString(), 1 + itemW * 2, base, mTextPaint);
         } else {
-            float dx = ps.mTraceX[count - 1] - ps.mTraceX[0];
-            float dy = ps.mTraceY[count - 1] - ps.mTraceY[0];
+            float dx = ps.mCurrentX - ps.mFirstX;
+            float dy = ps.mCurrentY - ps.mFirstY;
             canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom,
                     Math.abs(dx) < mVC.getScaledTouchSlop()
                             ? mTextBackgroundPaint : mTextLevelPaint);
@@ -565,9 +551,9 @@
                 .append(" TouchMinor=").append(coords.touchMinor, 3)
                 .append(" ToolMajor=").append(coords.toolMajor, 3)
                 .append(" ToolMinor=").append(coords.toolMinor, 3)
-                .append(" Orientation=").append((float)(coords.orientation * 180 / Math.PI), 1)
+                .append(" Orientation=").append((float) (coords.orientation * 180 / Math.PI), 1)
                 .append("deg")
-                .append(" Tilt=").append((float)(
+                .append(" Tilt=").append((float) (
                         coords.getAxisValue(MotionEvent.AXIS_TILT) * 180 / Math.PI), 1)
                 .append("deg")
                 .append(" Distance=").append(coords.getAxisValue(MotionEvent.AXIS_DISTANCE), 1)
@@ -598,6 +584,7 @@
                 mCurNumPointers = 0;
                 mMaxNumPointers = 0;
                 mVelocity.clear();
+                mTraceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
                 if (mAltVelocity != null) {
                     mAltVelocity.clear();
                 }
@@ -646,7 +633,8 @@
                     logCoords("Pointer", action, i, coords, id, event);
                 }
                 if (ps != null) {
-                    ps.addTrace(coords.x, coords.y, false);
+                    ps.addTrace(coords.x, coords.y, true);
+                    updateDrawTrace(ps);
                 }
             }
         }
@@ -659,7 +647,8 @@
                 logCoords("Pointer", action, i, coords, id, event);
             }
             if (ps != null) {
-                ps.addTrace(coords.x, coords.y, true);
+                ps.addTrace(coords.x, coords.y, false);
+                updateDrawTrace(ps);
                 ps.mXVelocity = mVelocity.getXVelocity(id);
                 ps.mYVelocity = mVelocity.getYVelocity(id);
                 if (mAltVelocity != null) {
@@ -702,13 +691,26 @@
                 if (mActivePointerId == id) {
                     mActivePointerId = event.getPointerId(index == 0 ? 1 : 0);
                 }
-                ps.addTrace(Float.NaN, Float.NaN, false);
+                ps.addTrace(Float.NaN, Float.NaN, true);
             }
         }
 
         invalidate();
     }
 
+    private void updateDrawTrace(PointerState ps) {
+        mPaint.setARGB(255, 128, 255, 255);
+        float x = ps.mCurrentX;
+        float y = ps.mCurrentY;
+        float lastX = ps.mPreviousX;
+        float lastY = ps.mPreviousY;
+        if (!Float.isNaN(x) && !Float.isNaN(y) && !Float.isNaN(lastX) && !Float.isNaN(lastY)) {
+            mTraceCanvas.drawLine(lastX, lastY, x, y, mPathPaint);
+            Paint paint = ps.mPreviousPointIsHistorical ? mPaint : mCurrentPointPaint;
+            mTraceCanvas.drawPoint(lastX, lastY, paint);
+        }
+    }
+
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         onPointerEvent(event);
@@ -767,7 +769,7 @@
                 return true;
             default:
                 return KeyEvent.isGamepadButton(keyCode)
-                    || KeyEvent.isModifierKey(keyCode);
+                        || KeyEvent.isModifierKey(keyCode);
         }
     }
 
@@ -887,7 +889,7 @@
         public FasterStringBuilder append(int value, int zeroPadWidth) {
             final boolean negative = value < 0;
             if (negative) {
-                value = - value;
+                value = -value;
                 if (value < 0) {
                     append("-2147483648");
                     return this;
@@ -973,26 +975,27 @@
 
     private ISystemGestureExclusionListener mSystemGestureExclusionListener =
             new ISystemGestureExclusionListener.Stub() {
-        @Override
-        public void onSystemGestureExclusionChanged(int displayId, Region systemGestureExclusion,
-                Region systemGestureExclusionUnrestricted) {
-            Region exclusion = Region.obtain(systemGestureExclusion);
-            Region rejected = Region.obtain();
-            if (systemGestureExclusionUnrestricted != null) {
-                rejected.set(systemGestureExclusionUnrestricted);
-                rejected.op(exclusion, Region.Op.DIFFERENCE);
-            }
-            Handler handler = getHandler();
-            if (handler != null) {
-                handler.post(() -> {
-                    mSystemGestureExclusion.set(exclusion);
-                    mSystemGestureExclusionRejected.set(rejected);
-                    exclusion.recycle();
-                    invalidate();
-                });
-            }
-        }
-    };
+                @Override
+                public void onSystemGestureExclusionChanged(int displayId,
+                        Region systemGestureExclusion,
+                        Region systemGestureExclusionUnrestricted) {
+                    Region exclusion = Region.obtain(systemGestureExclusion);
+                    Region rejected = Region.obtain();
+                    if (systemGestureExclusionUnrestricted != null) {
+                        rejected.set(systemGestureExclusionUnrestricted);
+                        rejected.op(exclusion, Region.Op.DIFFERENCE);
+                    }
+                    Handler handler = getHandler();
+                    if (handler != null) {
+                        handler.post(() -> {
+                            mSystemGestureExclusion.set(exclusion);
+                            mSystemGestureExclusionRejected.set(rejected);
+                            exclusion.recycle();
+                            invalidate();
+                        });
+                    }
+                }
+            };
 
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto
index bb654f0..d836563 100644
--- a/core/proto/android/service/graphicsstats.proto
+++ b/core/proto/android/service/graphicsstats.proto
@@ -62,6 +62,9 @@
 
     // HWUI renders pipeline type: GL or Vulkan
     optional PipelineType pipeline = 8;
+
+    // The UID of the app
+    optional int32 uid = 9;
 }
 
 message GraphicsStatsJankSummaryProto {
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 0837b45..0f73df9 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -37,6 +37,7 @@
 import static android.app.Notification.EXTRA_PICTURE_ICON;
 import static android.app.Notification.EXTRA_SUMMARY_TEXT;
 import static android.app.Notification.EXTRA_TITLE;
+import static android.app.Notification.FLAG_CAN_COLORIZE;
 import static android.app.Notification.GROUP_ALERT_CHILDREN;
 import static android.app.Notification.GROUP_ALERT_SUMMARY;
 import static android.app.Notification.GROUP_KEY_SILENT;
@@ -96,6 +97,7 @@
 import android.text.style.StyleSpan;
 import android.text.style.TextAppearanceSpan;
 import android.util.Pair;
+import android.util.Slog;
 import android.widget.RemoteViews;
 
 import androidx.test.InstrumentationRegistry;
@@ -126,6 +128,8 @@
 
     private Context mContext;
 
+    private RemoteViews mRemoteViews;
+
     @Rule
     public TestRule compatChangeRule = new PlatformCompatChangeRule();
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -133,23 +137,25 @@
     @Before
     public void setUp() {
         mContext = InstrumentationRegistry.getContext();
+        mRemoteViews = new RemoteViews(
+                mContext.getPackageName(), R.layout.notification_template_header);
     }
 
     @Test
     public void testColorizedByPermission() {
         Notification n = new Notification.Builder(mContext, "test")
-                .setFlag(Notification.FLAG_CAN_COLORIZE, true)
+                .setFlag(FLAG_CAN_COLORIZE, true)
                 .setColorized(true).setColor(Color.WHITE)
                 .build();
         assertTrue(n.isColorized());
 
         n = new Notification.Builder(mContext, "test")
-                .setFlag(Notification.FLAG_CAN_COLORIZE, true)
+                .setFlag(FLAG_CAN_COLORIZE, true)
                 .build();
         assertFalse(n.isColorized());
 
         n = new Notification.Builder(mContext, "test")
-                .setFlag(Notification.FLAG_CAN_COLORIZE, false)
+                .setFlag(FLAG_CAN_COLORIZE, false)
                 .setColorized(true).setColor(Color.WHITE)
                 .build();
         assertFalse(n.isColorized());
@@ -215,6 +221,275 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testHasTitle_noStyle() {
+        Notification n = new Notification.Builder(mContext, "test")
+                .setContentTitle("TITLE")
+                .build();
+        assertThat(n.hasTitle()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testHasTitle_bigText() {
+        Notification n = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+                .build();
+        assertThat(n.hasTitle()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testHasTitle_noTitle() {
+        Notification n = new Notification.Builder(mContext, "test")
+                .setContentText("text not title")
+                .build();
+        assertThat(n.hasTitle()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testContainsCustomViews_none() {
+        Notification np = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setContentText("test")
+                .build();
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setContentText("test")
+                .setPublicVersion(np)
+                .build();
+        assertThat(n.containsCustomViews()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testContainsCustomViews_content() {
+        Notification np = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setContentText("test")
+                .build();
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setContentText("test")
+                .setCustomContentView(mRemoteViews)
+                .setPublicVersion(np)
+                .build();
+        assertThat(n.containsCustomViews()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testContainsCustomViews_big() {
+        Notification np = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setContentText("test")
+                .build();
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setContentText("test")
+                .setCustomBigContentView(mRemoteViews)
+                .setPublicVersion(np)
+                .build();
+        assertThat(n.containsCustomViews()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testContainsCustomViews_headsUp() {
+        Notification np = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setContentText("test")
+                .build();
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setContentText("test")
+                .setCustomHeadsUpContentView(mRemoteViews)
+                .setPublicVersion(np)
+                .build();
+        assertThat(n.containsCustomViews()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testContainsCustomViews_content_public() {
+        Notification np = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setContentText("public")
+                .setCustomContentView(mRemoteViews)
+                .build();
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setContentText("test")
+                .setPublicVersion(np)
+                .build();
+        assertThat(n.containsCustomViews()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testContainsCustomViews_big_public() {
+        Notification np = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setContentText("test")
+                .setCustomBigContentView(mRemoteViews)
+                .build();
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setContentText("test")
+                .setPublicVersion(np)
+                .build();
+        assertThat(n.containsCustomViews()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testContainsCustomViews_headsUp_public() {
+        Notification np = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setContentText("test")
+                .setCustomHeadsUpContentView(mRemoteViews)
+                .build();
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setContentText("test")
+                .setPublicVersion(np)
+                .build();
+        assertThat(n.containsCustomViews()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testHasPromotableStyle_noStyle() {
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setContentText("test")
+                .build();
+        assertThat(n.hasPromotableStyle()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testHasPromotableStyle_bigPicture() {
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.BigPictureStyle())
+                .build();
+        assertThat(n.hasPromotableStyle()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testHasPromotableStyle_bigText() {
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.BigTextStyle())
+                .build();
+        assertThat(n.hasPromotableStyle()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testHasPromotableStyle_no_messagingStyle() {
+        Notification.MessagingStyle style = new Notification.MessagingStyle("self name")
+                .setGroupConversation(true)
+                .setConversationTitle("test conversation title");
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(style)
+                .build();
+        assertThat(n.hasPromotableStyle()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testHasPromotableStyle_no_mediaStyle() {
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.MediaStyle())
+                .build();
+        assertThat(n.hasPromotableStyle()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testHasPromotableStyle_no_inboxStyle() {
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.InboxStyle())
+                .build();
+        assertThat(n.hasPromotableStyle()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testHasPromotableStyle_callText() {
+        PendingIntent answerIntent = createPendingIntent("answer");
+        PendingIntent declineIntent = createPendingIntent("decline");
+        Notification.CallStyle style = Notification.CallStyle.forIncomingCall(
+                new Person.Builder().setName("A Caller").build(),
+                declineIntent,
+                answerIntent
+        );
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(style)
+                .build();
+        assertThat(n.hasPromotableStyle()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testHasPromotableCharacteristics() {
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+                .setColor(Color.WHITE)
+                .setColorized(true)
+                .setFlag(FLAG_CAN_COLORIZE, true)
+                .build();
+        assertThat(n.hasPromotableCharacteristics()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testHasPromotableCharacteristics_wrongStyle() {
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.InboxStyle())
+                .setContentTitle("TITLE")
+                .setColor(Color.WHITE)
+                .setColorized(true)
+                .setFlag(FLAG_CAN_COLORIZE, true)
+                .build();
+        assertThat(n.hasPromotableCharacteristics()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testHasPromotableCharacteristics_notColorized() {
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+                .setColor(Color.WHITE)
+                .build();
+        assertThat(n.hasPromotableCharacteristics()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testHasPromotableCharacteristics_noTitle() {
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.BigTextStyle())
+                .setColor(Color.WHITE)
+                .setColorized(true)
+                .setFlag(FLAG_CAN_COLORIZE, true)
+                .build();
+        assertThat(n.hasPromotableCharacteristics()).isFalse();
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
     public void testGetShortCriticalText_noneSet() {
         Notification n = new Notification.Builder(mContext, "test")
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 786f1e8..ba6f62c 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -38,7 +38,6 @@
 import android.graphics.RectF;
 import android.platform.test.annotations.Presubmit;
 import android.util.SparseArray;
-import android.view.SurfaceControl.Transaction;
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.animation.Interpolator;
 import android.view.animation.LinearInterpolator;
@@ -79,7 +78,6 @@
     private SurfaceControl mNavLeash;
     private InsetsState mInsetsState;
 
-    @Mock Transaction mMockTransaction;
     @Mock InsetsController mMockController;
     @Mock WindowInsetsAnimationControlListener mMockListener;
 
@@ -98,16 +96,14 @@
         mInsetsState.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars())
                 .setFrame(new Rect(400, 0, 500, 500));
         InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ID_STATUS_BAR,
-                WindowInsets.Type.statusBars(), mInsetsState,
-                () -> mMockTransaction, mMockController);
+                WindowInsets.Type.statusBars(), mInsetsState, mMockController);
         topConsumer.setControl(
                 new InsetsSourceControl(ID_STATUS_BAR, WindowInsets.Type.statusBars(),
                         mStatusLeash, true, new Point(0, 0), Insets.of(0, 100, 0, 0)),
                 new int[1], new int[1]);
 
         InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(ID_NAVIGATION_BAR,
-                WindowInsets.Type.navigationBars(), mInsetsState,
-                () -> mMockTransaction, mMockController);
+                WindowInsets.Type.navigationBars(), mInsetsState, mMockController);
         navConsumer.setControl(
                 new InsetsSourceControl(ID_NAVIGATION_BAR, WindowInsets.Type.navigationBars(),
                         mNavLeash, true, new Point(400, 0), Insets.of(0, 0, 100, 0)),
@@ -131,8 +127,9 @@
 
         mController = new InsetsAnimationControlImpl(controls,
                 new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
-                mMockController, spec /* insetsAnimationSpecCreator */, 0 /* animationType */,
-                0 /* layoutInsetsDuringAnimation */, null /* translator */, null /* statsToken */);
+                mMockController, mMockController, spec /* insetsAnimationSpecCreator */,
+                0 /* animationType */, 0 /* layoutInsetsDuringAnimation */, null /* translator */,
+                null /* statsToken */);
         mController.setReadyDispatched(true);
     }
 
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index bec8b1f..4516e9c 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -63,7 +63,6 @@
 import android.graphics.Rect;
 import android.os.CancellationSignal;
 import android.platform.test.annotations.Presubmit;
-import android.view.SurfaceControl.Transaction;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.OnControllableInsetsChangedListener;
 import android.view.WindowManager.BadTokenException;
@@ -138,8 +137,7 @@
             mTestHost = spy(new TestHost(mViewRoot));
             mController = new InsetsController(mTestHost, (controller, id, type) -> {
                 if (!Flags.refactorInsetsController() && type == ime()) {
-                    return new InsetsSourceConsumer(id, type, controller.getState(),
-                            Transaction::new, controller) {
+                    return new InsetsSourceConsumer(id, type, controller.getState(), controller) {
 
                         private boolean mImeRequestedShow;
 
@@ -155,8 +153,7 @@
                         }
                     };
                 } else {
-                    return new InsetsSourceConsumer(id, type, controller.getState(),
-                            Transaction::new, controller);
+                    return new InsetsSourceConsumer(id, type, controller.getState(), controller);
                 }
             }, mTestHandler);
             final Rect rect = new Rect(5, 5, 5, 5);
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 655cb45..d6d45e8 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -28,10 +28,7 @@
 import static junit.framework.TestCase.assertTrue;
 
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.app.Instrumentation;
 import android.content.Context;
@@ -39,7 +36,6 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
-import android.view.SurfaceControl.Transaction;
 import android.view.WindowManager.BadTokenException;
 import android.view.WindowManager.LayoutParams;
 import android.view.inputmethod.ImeTracker;
@@ -51,7 +47,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
@@ -75,9 +70,9 @@
 
     private SurfaceSession mSession = new SurfaceSession();
     private SurfaceControl mLeash;
-    @Mock Transaction mMockTransaction;
     private InsetsSource mSpyInsetsSource;
     private boolean mRemoveSurfaceCalled = false;
+    private boolean mSurfaceParamsApplied = false;
     private InsetsController mController;
     private InsetsState mState;
     private ViewRootImpl mViewRoot;
@@ -102,9 +97,14 @@
             mSpyInsetsSource = Mockito.spy(new InsetsSource(ID_STATUS_BAR, statusBars()));
             mState.addSource(mSpyInsetsSource);
 
-            mController = new InsetsController(new ViewRootInsetsControllerHost(mViewRoot));
-            mConsumer = new InsetsSourceConsumer(ID_STATUS_BAR, statusBars(), mState,
-                    () -> mMockTransaction, mController) {
+            mController = new InsetsController(new ViewRootInsetsControllerHost(mViewRoot)) {
+                @Override
+                public void applySurfaceParams(
+                        final SyncRtSurfaceTransactionApplier.SurfaceParams... params) {
+                    mSurfaceParamsApplied = true;
+                }
+            };
+            mConsumer = new InsetsSourceConsumer(ID_STATUS_BAR, statusBars(), mState, mController) {
                 @Override
                 public void removeSurface() {
                     super.removeSurface();
@@ -148,8 +148,7 @@
         InsetsState state = new InsetsState();
         InsetsController controller = new InsetsController(new ViewRootInsetsControllerHost(
                 mViewRoot));
-        InsetsSourceConsumer consumer = new InsetsSourceConsumer(
-                ID_IME, ime(), state, null, controller);
+        InsetsSourceConsumer consumer = new InsetsSourceConsumer(ID_IME, ime(), state, controller);
 
         InsetsSource source = new InsetsSource(ID_IME, ime());
         source.setFrame(0, 1, 2, 3);
@@ -182,9 +181,9 @@
     public void testRestore() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mConsumer.setControl(null, new int[1], new int[1]);
-            reset(mMockTransaction);
+            mSurfaceParamsApplied = false;
             mController.setRequestedVisibleTypes(0 /* visibleTypes */, statusBars());
-            verifyZeroInteractions(mMockTransaction);
+            assertFalse(mSurfaceParamsApplied);
             int[] hideTypes = new int[1];
             mConsumer.setControl(
                     new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash,
@@ -200,8 +199,9 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.setRequestedVisibleTypes(0 /* visibleTypes */, statusBars());
             mConsumer.setControl(null, new int[1], new int[1]);
-            reset(mMockTransaction);
-            verifyZeroInteractions(mMockTransaction);
+            mLeash = new SurfaceControl.Builder(mSession)
+                    .setName("testSurface")
+                    .build();
             mRemoveSurfaceCalled = false;
             int[] hideTypes = new int[1];
             mConsumer.setControl(
@@ -221,8 +221,7 @@
             ViewRootInsetsControllerHost host = new ViewRootInsetsControllerHost(mViewRoot);
             InsetsController insetsController = new InsetsController(host, (ic, id, type) -> {
                 if (type == ime()) {
-                    return new InsetsSourceConsumer(ID_IME, ime(), state,
-                            () -> mMockTransaction, ic) {
+                    return new InsetsSourceConsumer(ID_IME, ime(), state, ic) {
                         @Override
                         public int requestShow(boolean fromController,
                                 ImeTracker.Token statsToken) {
@@ -230,14 +229,14 @@
                         }
                     };
                 }
-                return new InsetsSourceConsumer(id, type, ic.getState(), Transaction::new, ic);
+                return new InsetsSourceConsumer(id, type, ic.getState(), ic);
             }, host.getHandler());
             InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(ID_IME, ime());
 
             // Initial IME insets source control with its leash.
             imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash,
                     false /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
-            reset(mMockTransaction);
+            mSurfaceParamsApplied = false;
 
             // Verify when the app requests controlling show IME animation, the IME leash
             // visibility won't be updated when the consumer received the same leash in setControl.
@@ -246,7 +245,7 @@
             assertEquals(ANIMATION_TYPE_USER, insetsController.getAnimationType(ime()));
             imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash,
                     true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
-            verify(mMockTransaction, never()).show(mLeash);
+            assertFalse(mSurfaceParamsApplied);
         });
     }
 }
diff --git a/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java b/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java
index 58834e6..a311d0d 100644
--- a/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java
+++ b/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java
@@ -23,7 +23,7 @@
 import static android.window.flags.DesktopModeFlags.ToggleOverride.fromSetting;
 
 import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE;
-import static com.android.window.flags.Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS;
+import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS;
 import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -198,145 +198,145 @@
 
     @Test
     @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
-            FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+            FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS})
     public void isTrue_dwFlagOn_overrideUnset_featureFlagOn_returnsTrue() {
         setOverride(OVERRIDE_UNSET_SETTING);
 
         // For unset overrides, follow flag
-        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()).isTrue();
+        assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
     }
 
     @Test
     @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
-    @DisableFlags(FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS)
     public void isTrue_dwFlagOn_overrideUnset_featureFlagOff_returnsFalse() {
         setOverride(OVERRIDE_UNSET_SETTING);
         // For unset overrides, follow flag
-        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()).isFalse();
+        assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
     }
 
     @Test
     @EnableFlags({
             FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
             FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
-            FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
+            FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS
     })
     public void isTrue_dwFlagOn_overrideOn_featureFlagOn_returnsTrue() {
         setOverride(OVERRIDE_ON_SETTING);
 
         // When toggle override matches its default state (dw flag), don't override flags
-        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()).isTrue();
+        assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
     }
 
     @Test
     @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
-    @DisableFlags(FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS)
     public void isTrue_dwFlagOn_overrideOn_featureFlagOff_returnsFalse() {
         setOverride(OVERRIDE_ON_SETTING);
 
         // When toggle override matches its default state (dw flag), don't override flags
-        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()).isFalse();
+        assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
     }
 
     @Test
     @EnableFlags({
             FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
             FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
-            FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
+            FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS
     })
     public void isTrue_dwFlagOn_overrideOff_featureFlagOn_returnsTrue() {
         setOverride(OVERRIDE_OFF_SETTING);
 
         // Follow override if they exist, and is not equal to default toggle state (dw flag)
-        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()).isTrue();
+        assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
     }
 
     @Test
     @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE})
-    @DisableFlags(FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS)
     public void isTrue_dwFlagOn_overrideOff_featureFlagOff_returnsFalse() {
         setOverride(OVERRIDE_OFF_SETTING);
 
         // Follow override if they exist, and is not equal to default toggle state (dw flag)
-        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()).isFalse();
+        assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
     }
 
     @Test
     @EnableFlags({
             FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
-            FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
+            FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS
     })
     @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void isTrue_dwFlagOff_overrideUnset_featureFlagOn_returnsTrue() {
         setOverride(OVERRIDE_UNSET_SETTING);
 
         // For unset overrides, follow flag
-        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()).isTrue();
+        assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
     }
 
     @Test
     @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
     @DisableFlags({
             FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
-            FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
+            FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS
     })
     public void isTrue_dwFlagOff_overrideUnset_featureFlagOff_returnsFalse() {
         setOverride(OVERRIDE_UNSET_SETTING);
 
         // For unset overrides, follow flag
-        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()).isFalse();
+        assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
     }
 
     @Test
     @EnableFlags({
             FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
-            FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
+            FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS
     })
     @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void isTrue_dwFlagOff_overrideOn_featureFlagOn_returnsTrue() {
         setOverride(OVERRIDE_ON_SETTING);
 
         // Follow override if they exist, and is not equal to default toggle state (dw flag)
-        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()).isTrue();
+        assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
     }
 
     @Test
     @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
     @DisableFlags({
             FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
-            FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
+            FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS
     })
     public void isTrue_dwFlagOff_overrideOn_featureFlagOff_returnFalse() {
         setOverride(OVERRIDE_ON_SETTING);
 
         // Follow override if they exist, and is not equal to default toggle state (dw flag)
-        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()).isFalse();
+        assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
     }
 
     @Test
     @EnableFlags({
             FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION,
-            FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
+            FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS
     })
     @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
     public void isTrue_dwFlagOff_overrideOff_featureFlagOn_returnsTrue() {
         setOverride(OVERRIDE_OFF_SETTING);
 
         // When toggle override matches its default state (dw flag), don't override flags
-        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()).isTrue();
+        assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue();
     }
 
     @Test
     @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
     @DisableFlags({
             FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
-            FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
+            FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS
     })
     public void isTrue_dwFlagOff_overrideOff_featureFlagOff_returnsFalse() {
         setOverride(OVERRIDE_OFF_SETTING);
 
         // When toggle override matches its default state (dw flag), don't override flags
-        assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()).isFalse();
+        assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
     }
 
     @Test
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index e07471c..6d31578 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -128,22 +128,6 @@
     private static final WeakHashMap<Bitmap, Void> sAllBitmaps = new WeakHashMap<>();
 
     /**
-     * @hide
-     */
-    private static NativeAllocationRegistry getRegistry(boolean malloc, long size) {
-        final long free = nativeGetNativeFinalizer();
-        if (com.android.libcore.Flags.nativeMetrics()) {
-            Class cls = Bitmap.class;
-            return malloc ? NativeAllocationRegistry.createMalloced(cls, free, size)
-                          : NativeAllocationRegistry.createNonmalloced(cls, free, size);
-        } else {
-            ClassLoader loader = Bitmap.class.getClassLoader();
-            return malloc ? NativeAllocationRegistry.createMalloced(loader, free, size)
-                          : NativeAllocationRegistry.createNonmalloced(loader, free, size);
-        }
-    }
-
-    /**
      * Private constructor that must receive an already allocated native bitmap
      * int (pointer).
      */
@@ -167,6 +151,7 @@
         mWidth = width;
         mHeight = height;
         mRequestPremultiplied = requestPremultiplied;
+
         mNinePatchChunk = ninePatchChunk;
         mNinePatchInsets = ninePatchInsets;
         if (density >= 0) {
@@ -174,9 +159,17 @@
         }
 
         mNativePtr = nativeBitmap;
-        final int allocationByteCount = getAllocationByteCount();
-        getRegistry(fromMalloc, allocationByteCount).registerNativeAllocation(this, mNativePtr);
 
+        final int allocationByteCount = getAllocationByteCount();
+        NativeAllocationRegistry registry;
+        if (fromMalloc) {
+            registry = NativeAllocationRegistry.createMalloced(
+                    Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount);
+        } else {
+            registry = NativeAllocationRegistry.createNonmalloced(
+                    Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount);
+        }
+        registry.registerNativeAllocation(this, nativeBitmap);
         synchronized (Bitmap.class) {
           sAllBitmaps.put(this, null);
         }
diff --git a/graphics/java/android/graphics/GraphicsStatsService.java b/graphics/java/android/graphics/GraphicsStatsService.java
index dc785c5..7a012bc 100644
--- a/graphics/java/android/graphics/GraphicsStatsService.java
+++ b/graphics/java/android/graphics/GraphicsStatsService.java
@@ -311,7 +311,7 @@
                 Log.w(TAG, "Unable to create path: '" + parent.getAbsolutePath() + "'");
                 return;
             }
-            nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.mPackageName,
+            nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.mUid, buffer.mInfo.mPackageName,
                     buffer.mInfo.mVersionCode, buffer.mInfo.mStartTime, buffer.mInfo.mEndTime,
                     buffer.mData);
         }
@@ -405,7 +405,7 @@
             HistoricalBuffer buffer = buffers.get(i);
             File path = pathForApp(buffer.mInfo);
             skipFiles.add(path);
-            nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.mPackageName,
+            nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.mUid, buffer.mInfo.mPackageName,
                     buffer.mInfo.mVersionCode,  buffer.mInfo.mStartTime, buffer.mInfo.mEndTime,
                     buffer.mData);
         }
@@ -469,21 +469,23 @@
 
     private static native int nGetAshmemSize();
     private static native long nCreateDump(int outFd, boolean isProto);
-    private static native void nAddToDump(long dump, String path, String packageName,
+    private static native void nAddToDump(long dump, String path, int uid, String packageName,
             long versionCode, long startTime, long endTime, byte[] data);
     private static native void nAddToDump(long dump, String path);
     private static native void nFinishDump(long dump);
     private static native void nFinishDumpInMemory(long dump, long pulledData, boolean lastFullDay);
-    private static native void nSaveBuffer(String path, String packageName, long versionCode,
-            long startTime, long endTime, byte[] data);
+    private static native void nSaveBuffer(String path, int uid, String packageName,
+            long versionCode, long startTime, long endTime, byte[] data);
 
     private final class BufferInfo {
+        final int mUid;
         final String mPackageName;
         final long mVersionCode;
         long mStartTime;
         long mEndTime;
 
-        BufferInfo(String packageName, long versionCode, long startTime) {
+        BufferInfo(int uid, String packageName, long versionCode, long startTime) {
+            this.mUid = uid;
             this.mPackageName = packageName;
             this.mVersionCode = versionCode;
             this.mStartTime = startTime;
@@ -502,7 +504,7 @@
         ActiveBuffer(IGraphicsStatsCallback token, int uid, int pid, String packageName,
                 long versionCode)
                 throws RemoteException, IOException {
-            mInfo = new BufferInfo(packageName, versionCode, System.currentTimeMillis());
+            mInfo = new BufferInfo(uid, packageName, versionCode, System.currentTimeMillis());
             mUid = uid;
             mPid = pid;
             mCallback = token;
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 94809f2..f857429 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -147,8 +147,10 @@
 java_library {
     name: "WindowManager-Shell-lite-proto",
 
-    srcs: ["src/com/android/wm/shell/desktopmode/education/data/proto/**/*.proto"],
-
+    srcs: [
+        "src/com/android/wm/shell/desktopmode/education/data/proto/**/*.proto",
+        "src/com/android/wm/shell/desktopmode/persistence/*.proto",
+    ],
     proto: {
         type: "lite",
     },
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml
index c0ff192..1d1cdfa 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml
@@ -28,6 +28,8 @@
         android:layout_height="@dimen/desktop_mode_fullscreen_decor_caption_height"
         android:paddingVertical="16dp"
         android:paddingHorizontal="10dp"
+        android:screenReaderFocusable="true"
+        android:importantForAccessibility="yes"
         android:contentDescription="@string/handle_text"
         android:src="@drawable/decor_handle_dark"
         tools:tint="@color/desktop_mode_caption_handle_bar_dark"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml
index 7dcb3c2..3dbf754 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml
@@ -31,14 +31,16 @@
         android:orientation="horizontal"
         android:clickable="true"
         android:focusable="true"
+        android:contentDescription="@string/desktop_mode_app_header_chip_text"
         android:layout_marginStart="12dp">
         <ImageView
             android:id="@+id/application_icon"
             android:layout_width="@dimen/desktop_mode_caption_icon_radius"
             android:layout_height="@dimen/desktop_mode_caption_icon_radius"
             android:layout_gravity="center_vertical"
-            android:contentDescription="@string/app_icon_text"
             android:layout_marginStart="6dp"
+            android:clickable="false"
+            android:focusable="false"
             android:scaleType="centerCrop"/>
 
         <TextView
@@ -53,18 +55,22 @@
             android:layout_gravity="center_vertical"
             android:layout_weight="1"
             android:layout_marginStart="8dp"
+            android:clickable="false"
+            android:focusable="false"
             tools:text="Gmail"/>
 
         <ImageButton
             android:id="@+id/expand_menu_button"
             android:layout_width="16dp"
             android:layout_height="16dp"
-            android:contentDescription="@string/expand_menu_text"
             android:src="@drawable/ic_baseline_expand_more_24"
             android:background="@null"
             android:scaleType="fitCenter"
             android:clickable="false"
             android:focusable="false"
+            android:screenReaderFocusable="false"
+            android:importantForAccessibility="no"
+            android:contentDescription="@null"
             android:layout_marginHorizontal="8dp"
             android:layout_gravity="center_vertical"/>
 
@@ -90,6 +96,7 @@
 
     <com.android.wm.shell.windowdecor.MaximizeButtonView
         android:id="@+id/maximize_button_view"
+        android:importantForAccessibility="no"
         android:layout_width="44dp"
         android:layout_height="40dp"
         android:layout_gravity="end"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index 64f71c7..6913e54 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -43,13 +43,15 @@
             android:layout_height="@dimen/desktop_mode_caption_icon_radius"
             android:layout_marginStart="12dp"
             android:layout_marginEnd="12dp"
-            android:contentDescription="@string/app_icon_text"/>
+            android:contentDescription="@string/app_icon_text"
+            android:importantForAccessibility="no"/>
 
         <TextView
             android:id="@+id/application_name"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             tools:text="Gmail"
+            android:importantForAccessibility="no"
             android:textColor="?androidprv:attr/materialColorOnSurface"
             android:textSize="14sp"
             android:textFontWeight="500"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
index 5fe3f2a..35ef239 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
@@ -41,6 +41,8 @@
                 android:id="@+id/maximize_menu_maximize_button"
                 style="?android:attr/buttonBarButtonStyle"
                 android:stateListAnimator="@null"
+                android:importantForAccessibility="yes"
+                android:contentDescription="@string/desktop_mode_maximize_menu_maximize_button_text"
                 android:layout_marginRight="8dp"
                 android:layout_marginBottom="4dp"
                 android:alpha="0"/>
@@ -53,6 +55,7 @@
                 android:layout_marginBottom="76dp"
                 android:gravity="center"
                 android:fontFamily="google-sans-text"
+                android:importantForAccessibility="no"
                 android:text="@string/desktop_mode_maximize_menu_maximize_text"
                 android:textColor="?androidprv:attr/materialColorOnSurface"
                 android:alpha="0"/>
@@ -78,6 +81,8 @@
                     android:layout_height="@dimen/desktop_mode_maximize_menu_button_height"
                     android:layout_marginRight="4dp"
                     android:background="@drawable/desktop_mode_maximize_menu_button_background"
+                    android:importantForAccessibility="yes"
+                    android:contentDescription="@string/desktop_mode_maximize_menu_snap_left_button_text"
                     android:stateListAnimator="@null"/>
 
                 <Button
@@ -86,6 +91,8 @@
                     android:layout_width="41dp"
                     android:layout_height="@dimen/desktop_mode_maximize_menu_button_height"
                     android:background="@drawable/desktop_mode_maximize_menu_button_background"
+                    android:importantForAccessibility="yes"
+                    android:contentDescription="@string/desktop_mode_maximize_menu_snap_right_button_text"
                     android:stateListAnimator="@null"/>
             </LinearLayout>
             <TextView
@@ -96,6 +103,7 @@
                 android:layout_marginBottom="76dp"
                 android:layout_gravity="center"
                 android:gravity="center"
+                android:importantForAccessibility="no"
                 android:fontFamily="google-sans-text"
                 android:text="@string/desktop_mode_maximize_menu_snap_text"
                 android:textColor="?androidprv:attr/materialColorOnSurface"
diff --git a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
index cf1b894..b734d2d 100644
--- a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
+++ b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
@@ -19,7 +19,8 @@
 
     <FrameLayout
         android:layout_width="44dp"
-        android:layout_height="40dp">
+        android:layout_height="40dp"
+        android:importantForAccessibility="noHideDescendants">
         <ProgressBar
             android:id="@+id/progress_bar"
             style="?android:attr/progressBarStyleHorizontal"
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index a6da421..bda5686 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -300,12 +300,19 @@
     <string name="close_text">Close</string>
     <!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] -->
     <string name="collapse_menu_text">Close Menu</string>
-    <!-- Accessibility text for the handle menu open menu button [CHAR LIMIT=NONE] -->
-    <string name="expand_menu_text">Open Menu</string>
+    <!-- Accessibility text for the App Header's App Chip [CHAR LIMIT=NONE] -->
+    <string name="desktop_mode_app_header_chip_text">Open Menu</string>
     <!-- Maximize menu maximize button string. -->
     <string name="desktop_mode_maximize_menu_maximize_text">Maximize Screen</string>
     <!-- Maximize menu snap buttons string. -->
     <string name="desktop_mode_maximize_menu_snap_text">Snap Screen</string>
     <!-- Snap resizing non-resizable string. -->
     <string name="desktop_mode_non_resizable_snap_text">This app can\'t be resized</string>
+    <!-- Accessibility text for the Maximize Menu's maximize button [CHAR LIMIT=NONE] -->
+    <string name="desktop_mode_maximize_menu_maximize_button_text">Maximize</string>
+    <!-- Accessibility text for the Maximize Menu's snap left button [CHAR LIMIT=NONE] -->
+    <string name="desktop_mode_maximize_menu_snap_left_button_text">Snap left</string>
+    <!-- Accessibility text for the Maximize Menu's snap right button [CHAR LIMIT=NONE] -->
+    <string name="desktop_mode_maximize_menu_snap_right_button_text">Snap right</string>
+
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 7e6f434..4607a8e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -584,7 +584,8 @@
             final boolean windowModeChanged =
                     data.getTaskInfo().getWindowingMode() != taskInfo.getWindowingMode();
             final boolean visibilityChanged = data.getTaskInfo().isVisible != taskInfo.isVisible;
-            if (windowModeChanged || visibilityChanged) {
+            if (windowModeChanged || (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+                    && visibilityChanged)) {
                 mRecentTasks.ifPresent(recentTasks ->
                         recentTasks.onTaskRunningInfoChanged(taskInfo));
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 80a9b67..308bd0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -79,6 +79,7 @@
 import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
 import com.android.wm.shell.desktopmode.education.AppHandleEducationFilter;
 import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository;
+import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.draganddrop.GlobalDragListener;
 import com.android.wm.shell.freeform.FreeformComponents;
@@ -712,8 +713,14 @@
     @WMSingleton
     @Provides
     @DynamicOverride
-    static DesktopModeTaskRepository provideDesktopModeTaskRepository() {
-        return new DesktopModeTaskRepository();
+    static DesktopModeTaskRepository provideDesktopModeTaskRepository(
+            Context context,
+            ShellInit shellInit,
+            DesktopPersistentRepository desktopPersistentRepository,
+            @ShellMainThread CoroutineScope mainScope
+    ) {
+        return new DesktopModeTaskRepository(context, shellInit, desktopPersistentRepository,
+                mainScope);
     }
 
     @WMSingleton
@@ -798,6 +805,14 @@
                 shellTaskOrganizer, appHandleEducationDatastoreRepository, applicationScope);
     }
 
+    @WMSingleton
+    @Provides
+    static DesktopPersistentRepository provideDesktopPersistentRepository(
+            Context context,
+            @ShellBackgroundThread CoroutineScope bgScope) {
+        return new DesktopPersistentRepository(context, bgScope);
+    }
+
     //
     // Drag and drop
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 9d04169..759ed03 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.desktopmode
 
+import android.content.Context
 import android.graphics.Rect
 import android.graphics.Region
 import android.util.ArrayMap
@@ -27,13 +28,27 @@
 import androidx.core.util.keyIterator
 import androidx.core.util.valueIterator
 import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags
+import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
+import com.android.wm.shell.desktopmode.persistence.DesktopTask
+import com.android.wm.shell.desktopmode.persistence.DesktopTaskState
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.sysui.ShellInit
 import java.io.PrintWriter
 import java.util.concurrent.Executor
 import java.util.function.Consumer
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 
 /** Tracks task data for Desktop Mode. */
-class DesktopModeTaskRepository {
+class DesktopModeTaskRepository (
+    private val context: Context,
+    shellInit: ShellInit,
+    private val persistentRepository: DesktopPersistentRepository,
+    @ShellMainThread private val mainCoroutineScope: CoroutineScope,
+){
 
     /**
      * Task data tracked per desktop.
@@ -54,7 +69,15 @@
         // TODO(b/332682201): Remove when the repository state is updated via TransitionObserver
         val closingTasks: ArraySet<Int> = ArraySet(),
         val freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
-    )
+    ) {
+        fun deepCopy(): DesktopTaskData = DesktopTaskData(
+            activeTasks = ArraySet(activeTasks),
+            visibleTasks = ArraySet(visibleTasks),
+            minimizedTasks = ArraySet(minimizedTasks),
+            closingTasks = ArraySet(closingTasks),
+            freeformTasksInZOrder = ArrayList(freeformTasksInZOrder)
+        )
+    }
 
     /* Current wallpaper activity token to remove wallpaper activity when last task is removed. */
     var wallpaperActivityToken: WindowContainerToken? = null
@@ -77,6 +100,40 @@
             this[displayId] ?: DesktopTaskData().also { this[displayId] = it }
     }
 
+    init {
+        if (DesktopModeStatus.canEnterDesktopMode(context)) {
+            shellInit.addInitCallback(::initRepoFromPersistentStorage, this)
+        }
+    }
+
+    private fun initRepoFromPersistentStorage() {
+        if (!Flags.enableDesktopWindowingPersistence()) return
+        //  TODO: b/365962554 - Handle the case that user moves to desktop before it's initialized
+        mainCoroutineScope.launch {
+            val desktop = persistentRepository.readDesktop()
+            val maxTasks =
+                DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 }
+                    ?: desktop.zOrderedTasksCount
+
+            desktop.zOrderedTasksList
+                // Reverse it so we initialize the repo from bottom to top.
+                .reversed()
+                .map { taskId ->
+                    desktop.tasksByTaskIdMap.getOrDefault(
+                        taskId,
+                        DesktopTask.getDefaultInstance()
+                    )
+                }
+                .filter { task -> task.desktopTaskState == DesktopTaskState.VISIBLE }
+                .take(maxTasks)
+                .forEach { task ->
+                    addOrMoveFreeformTaskToTop(desktop.displayId, task.taskId)
+                    addActiveTask(desktop.displayId, task.taskId)
+                    updateTaskVisibility(desktop.displayId, task.taskId, visible = false)
+                }
+        }
+    }
+
     /** Adds [activeTasksListener] to be notified of updates to active tasks. */
     fun addActiveTaskListener(activeTasksListener: ActiveTasksListener) {
         activeTasksListeners.add(activeTasksListener)
@@ -266,12 +323,18 @@
         desktopTaskDataByDisplayId.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId)
         // Unminimize the task if it is minimized.
         unminimizeTask(displayId, taskId)
+        if (Flags.enableDesktopWindowingPersistence()) {
+            updatePersistentRepository(displayId)
+        }
     }
 
     /** Minimizes the task for [taskId] and [displayId] */
     fun minimizeTask(displayId: Int, taskId: Int) {
         logD("Minimize Task: display=%d, task=%d", displayId, taskId)
         desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId)
+        if (Flags.enableDesktopWindowingPersistence()) {
+            updatePersistentRepository(displayId)
+        }
     }
 
     /** Unminimizes the task for [taskId] and [displayId] */
@@ -315,7 +378,10 @@
         // Remove task from unminimized task if it is minimized.
         unminimizeTask(displayId, taskId)
         removeActiveTask(taskId)
-        updateTaskVisibility(displayId, taskId, visible = false);
+        updateTaskVisibility(displayId, taskId, visible = false)
+        if (Flags.enableDesktopWindowingPersistence()) {
+            updatePersistentRepository(displayId)
+        }
     }
 
     /**
@@ -352,6 +418,27 @@
     fun saveBoundsBeforeMaximize(taskId: Int, bounds: Rect) =
         boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds))
 
+    private fun updatePersistentRepository(displayId: Int) {
+        // Create a deep copy of the data
+        desktopTaskDataByDisplayId[displayId]?.deepCopy()?.let { desktopTaskDataByDisplayIdCopy ->
+            mainCoroutineScope.launch {
+                try {
+                    persistentRepository.addOrUpdateDesktop(
+                        visibleTasks = desktopTaskDataByDisplayIdCopy.visibleTasks,
+                        minimizedTasks = desktopTaskDataByDisplayIdCopy.minimizedTasks,
+                        freeformTasksInZOrder = desktopTaskDataByDisplayIdCopy.freeformTasksInZOrder
+                    )
+                } catch (exception: Exception) {
+                    logE(
+                        "An exception occurred while updating the persistent repository \n%s",
+                        exception.stackTrace
+                    )
+                }
+            }
+        }
+    }
+
+
     internal fun dump(pw: PrintWriter, prefix: String) {
         val innerPrefix = "$prefix  "
         pw.println("${prefix}DesktopModeTaskRepository")
@@ -390,6 +477,10 @@
         ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
     }
 
+    private fun logE(msg: String, vararg arguments: Any?) {
+        ProtoLog.e(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+    }
+
     companion object {
         private const val TAG = "DesktopModeTaskRepository"
     }
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 f3ae3ed..968f40c 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
@@ -58,6 +58,7 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.policy.ScreenDecorationsUtils
 import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.common.DisplayController
@@ -80,8 +81,8 @@
 import com.android.wm.shell.recents.RecentTasksController
 import com.android.wm.shell.recents.RecentsTransitionHandler
 import com.android.wm.shell.recents.RecentsTransitionStateListener
-import com.android.wm.shell.shared.TransitionUtil
 import com.android.wm.shell.shared.ShellSharedConstants
+import com.android.wm.shell.shared.TransitionUtil
 import com.android.wm.shell.shared.annotations.ExternalThread
 import com.android.wm.shell.shared.annotations.ShellMainThread
 import android.window.flags.DesktopModeFlags
@@ -728,7 +729,7 @@
                 // exclude current task since maximize/restore transition has not taken place yet.
                 .filterNot { taskId -> taskId == excludeTaskId }
                 .any { taskId ->
-                    val taskInfo = shellTaskOrganizer.getRunningTaskInfo(taskId)!!
+                    val taskInfo = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: return false
                     val displayLayout = displayController.getDisplayLayout(taskInfo.displayId)
                     val stableBounds = Rect().apply { displayLayout?.getStableBounds(this) }
                     logD("taskInfo = %s", taskInfo)
@@ -896,6 +897,7 @@
         val nonMinimizedTasksOrderedFrontToBack =
             taskRepository.getActiveNonMinimizedOrderedTasks(displayId)
         // If we're adding a new Task we might need to minimize an old one
+        // TODO(b/365725441): Handle non running task minimization
         val taskToMinimize: RunningTaskInfo? =
             if (newTaskIdInFront != null && desktopTasksLimiter.isPresent) {
                 desktopTasksLimiter
@@ -907,12 +909,26 @@
             } else {
                 null
             }
+
         nonMinimizedTasksOrderedFrontToBack
             // If there is a Task to minimize, let it stay behind the Home Task
             .filter { taskId -> taskId != taskToMinimize?.taskId }
-            .mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) }
             .reversed() // Start from the back so the front task is brought forward last
-            .forEach { task -> wct.reorder(task.token, /* onTop= */ true) }
+            .forEach { taskId ->
+                val runningTaskInfo = shellTaskOrganizer.getRunningTaskInfo(taskId)
+                if (runningTaskInfo != null) {
+                    // Task is already running, reorder it to the front
+                    wct.reorder(runningTaskInfo.token, /* onTop= */ true)
+                } else if (Flags.enableDesktopWindowingPersistence()) {
+                    // Task is not running, start it
+                    wct.startTask(
+                        taskId,
+                        ActivityOptions.makeBasic().apply {
+                            launchWindowingMode = WINDOWING_MODE_FREEFORM
+                        }.toBundle(),
+                    )
+                }
+            }
 
         taskbarDesktopTaskListener?.
             onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding(displayId))
@@ -1211,6 +1227,7 @@
             wct.reorder(task.token, true)
             return wct
         }
+        // TODO(b/365723620): Handle non running tasks that were launched after reboot.
         // If task is already visible, it must have been handled already and added to desktop mode.
         // Cascade task only if it's not visible yet.
         if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
new file mode 100644
index 0000000..3f41d7c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
@@ -0,0 +1,201 @@
+/*
+ * 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.desktopmode.persistence
+
+import android.content.Context
+import android.util.ArraySet
+import android.util.Log
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.datastore.core.CorruptionException
+import androidx.datastore.core.DataStore
+import androidx.datastore.core.DataStoreFactory
+import androidx.datastore.core.Serializer
+import androidx.datastore.dataStoreFile
+import com.android.framework.protobuf.InvalidProtocolBufferException
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.first
+
+/**
+ * Persistent repository for storing desktop mode related data.
+ *
+ * The main constructor is public only for testing purposes.
+ */
+class DesktopPersistentRepository(
+    private val dataStore: DataStore<DesktopPersistentRepositories>,
+) {
+    constructor(
+        context: Context,
+        @ShellBackgroundThread bgCoroutineScope: CoroutineScope,
+    ) : this(
+        DataStoreFactory.create(
+            serializer = DesktopPersistentRepositoriesSerializer,
+            produceFile = { context.dataStoreFile(DESKTOP_REPOSITORIES_DATASTORE_FILE) },
+            scope = bgCoroutineScope))
+
+    /** Provides `dataStore.data` flow and handles exceptions thrown during collection */
+    private val dataStoreFlow: Flow<DesktopPersistentRepositories> =
+        dataStore.data.catch { exception ->
+            // dataStore.data throws an IOException when an error is encountered when reading data
+            if (exception is IOException) {
+                Log.e(
+                    TAG,
+                    "Error in reading desktop mode related data from datastore, data is " +
+                        "stored in a file named $DESKTOP_REPOSITORIES_DATASTORE_FILE",
+                    exception)
+            } else {
+                throw exception
+            }
+        }
+
+    /**
+     * Reads and returns the [DesktopRepositoryState] proto object from the DataStore for a user. If
+     * the DataStore is empty or there's an error reading, it returns the default value of Proto.
+     */
+    private suspend fun getDesktopRepositoryState(
+        userId: Int = DEFAULT_USER_ID
+    ): DesktopRepositoryState =
+        try {
+            dataStoreFlow
+                .first()
+                .desktopRepoByUserMap
+                .getOrDefault(userId, DesktopRepositoryState.getDefaultInstance())
+        } catch (e: Exception) {
+            Log.e(TAG, "Unable to read from datastore", e)
+            DesktopRepositoryState.getDefaultInstance()
+        }
+
+    /**
+     * Reads the [Desktop] of a desktop filtering by the [userId] and [desktopId]. Executes the
+     * [callback] using the [mainCoroutineScope].
+     */
+    suspend fun readDesktop(
+        userId: Int = DEFAULT_USER_ID,
+        desktopId: Int = DEFAULT_DESKTOP_ID,
+    ): Desktop =
+        try {
+            val repository = getDesktopRepositoryState(userId)
+            repository.getDesktopOrThrow(desktopId)
+        } catch (e: Exception) {
+            Log.e(TAG, "Unable to get desktop info from persistent repository", e)
+            Desktop.getDefaultInstance()
+        }
+
+    /** Adds or updates a desktop stored in the datastore */
+    suspend fun addOrUpdateDesktop(
+        userId: Int = DEFAULT_USER_ID,
+        desktopId: Int = 0,
+        visibleTasks: ArraySet<Int> = ArraySet(),
+        minimizedTasks: ArraySet<Int> = ArraySet(),
+        freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
+    ) {
+        // TODO: b/367609270 - Improve the API to support multi-user
+        try {
+            dataStore.updateData { desktopPersistentRepositories: DesktopPersistentRepositories ->
+                val currentRepository =
+                    desktopPersistentRepositories.getDesktopRepoByUserOrDefault(
+                        userId, DesktopRepositoryState.getDefaultInstance())
+                val desktop =
+                    getDesktop(currentRepository, desktopId)
+                        .toBuilder()
+                        .updateTaskStates(visibleTasks, minimizedTasks)
+                        .updateZOrder(freeformTasksInZOrder)
+
+                desktopPersistentRepositories
+                    .toBuilder()
+                    .putDesktopRepoByUser(
+                        userId,
+                        currentRepository
+                            .toBuilder()
+                            .putDesktop(desktopId, desktop.build())
+                            .build())
+                    .build()
+            }
+        } catch (exception: IOException) {
+            Log.e(
+                TAG,
+                "Error in updating desktop mode related data, data is " +
+                    "stored in a file named $DESKTOP_REPOSITORIES_DATASTORE_FILE",
+                exception)
+        }
+    }
+
+    private fun getDesktop(currentRepository: DesktopRepositoryState, desktopId: Int): Desktop =
+        // If there are no desktops set up, create one on the default display
+        currentRepository.getDesktopOrDefault(
+            desktopId,
+            Desktop.newBuilder().setDesktopId(desktopId).setDisplayId(DEFAULT_DISPLAY).build())
+
+    companion object {
+        private const val TAG = "DesktopPersistenceRepo"
+        private const val DESKTOP_REPOSITORIES_DATASTORE_FILE = "desktop_persistent_repositories.pb"
+
+        private const val DEFAULT_USER_ID = 1000
+        private const val DEFAULT_DESKTOP_ID = 0
+
+        object DesktopPersistentRepositoriesSerializer : Serializer<DesktopPersistentRepositories> {
+
+            override val defaultValue: DesktopPersistentRepositories =
+                DesktopPersistentRepositories.getDefaultInstance()
+
+            override suspend fun readFrom(input: InputStream): DesktopPersistentRepositories =
+                try {
+                    DesktopPersistentRepositories.parseFrom(input)
+                } catch (exception: InvalidProtocolBufferException) {
+                    throw CorruptionException("Cannot read proto.", exception)
+                }
+
+            override suspend fun writeTo(t: DesktopPersistentRepositories, output: OutputStream) =
+                t.writeTo(output)
+        }
+
+        private fun Desktop.Builder.updateTaskStates(
+            visibleTasks: ArraySet<Int>,
+            minimizedTasks: ArraySet<Int>
+        ): Desktop.Builder {
+            clearTasksByTaskId()
+            putAllTasksByTaskId(
+                visibleTasks.associateWith {
+                    createDesktopTask(it, state = DesktopTaskState.VISIBLE)
+                })
+            putAllTasksByTaskId(
+                minimizedTasks.associateWith {
+                    createDesktopTask(it, state = DesktopTaskState.MINIMIZED)
+                })
+            return this
+        }
+
+        private fun Desktop.Builder.updateZOrder(
+            freeformTasksInZOrder: ArrayList<Int>
+        ): Desktop.Builder {
+            clearZOrderedTasks()
+            addAllZOrderedTasks(freeformTasksInZOrder)
+            return this
+        }
+
+        private fun createDesktopTask(
+            taskId: Int,
+            state: DesktopTaskState = DesktopTaskState.VISIBLE
+        ): DesktopTask =
+            DesktopTask.newBuilder().setTaskId(taskId).setDesktopTaskState(state).build()
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/persistent_desktop_repositories.proto b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/persistent_desktop_repositories.proto
new file mode 100644
index 0000000..0105231
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/persistent_desktop_repositories.proto
@@ -0,0 +1,33 @@
+syntax = "proto2";
+
+option java_package = "com.android.wm.shell.desktopmode.persistence";
+option java_multiple_files = true;
+
+// Represents the state of a task in desktop.
+enum DesktopTaskState {
+  VISIBLE = 0;
+  MINIMIZED = 1;
+}
+
+message DesktopTask {
+  optional int32 task_id = 1;
+  optional DesktopTaskState desktop_task_state= 2;
+}
+
+message Desktop {
+  optional int32 display_id = 1;
+  optional int32 desktop_id = 2;
+  // Stores a mapping between task id and the tasks. The key is the task id.
+  map<int32, DesktopTask> tasks_by_task_id = 3;
+  repeated int32 z_ordered_tasks = 4;
+}
+
+message DesktopRepositoryState {
+  // Stores a mapping between a repository and the desktops in it. The key is the desktop id.
+  map<int32, Desktop> desktop = 1;
+}
+
+message DesktopPersistentRepositories {
+  // Stores a mapping between a user and their desktop repository. The key is the user id.
+  map<int32, DesktopRepositoryState> desktop_repo_by_user = 1;
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 1573291..3a2820ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -358,12 +358,9 @@
 
             if (mode == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) {
                 if (info.getType() == TRANSIT_CHANGE) {
-                    int anim = getRotationAnimationHint(change, info, mDisplayController);
+                    final int anim = getRotationAnimationHint(change, info, mDisplayController);
                     isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS;
                     if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
-                        if (wallpaperTransit != WALLPAPER_TRANSITION_NONE) {
-                            anim |= ScreenRotationAnimation.ANIMATION_HINT_HAS_WALLPAPER;
-                        }
                         startRotationAnimation(startTransaction, change, info, anim, animations,
                                 onAnimFinish);
                         isDisplayRotationAnimationStarted = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index b9d11a3..5802e2c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -25,9 +25,12 @@
 import static com.android.wm.shell.transition.Transitions.TAG;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ArgbEvaluator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.content.Context;
+import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
@@ -35,7 +38,6 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
-import android.view.animation.AccelerateInterpolator;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 import android.window.ScreenCapture;
@@ -72,9 +74,6 @@
  */
 class ScreenRotationAnimation {
     static final int MAX_ANIMATION_DURATION = 10 * 1000;
-    static final int ANIMATION_HINT_HAS_WALLPAPER = 1 << 8;
-    /** It must cover all WindowManager#ROTATION_ANIMATION_*. */
-    private static final int ANIMATION_TYPE_MASK = 0xff;
 
     private final Context mContext;
     private final TransactionPool mTransactionPool;
@@ -82,7 +81,7 @@
     /** The leash of the changing window container. */
     private final SurfaceControl mSurfaceControl;
 
-    private final int mAnimType;
+    private final int mAnimHint;
     private final int mStartWidth;
     private final int mStartHeight;
     private final int mEndWidth;
@@ -99,12 +98,6 @@
     private SurfaceControl mBackColorSurface;
     /** The leash using to animate screenshot layer. */
     private final SurfaceControl mAnimLeash;
-    /**
-     * The container with background color for {@link #mSurfaceControl}. It is only created if
-     * {@link #mSurfaceControl} may be translucent. E.g. visible wallpaper with alpha < 1 (dimmed).
-     * That prevents flickering of alpha blending.
-     */
-    private SurfaceControl mBackEffectSurface;
 
     // The current active animation to move from the old to the new rotated
     // state.  Which animation is run here will depend on the old and new
@@ -122,7 +115,7 @@
             Transaction t, TransitionInfo.Change change, SurfaceControl rootLeash, int animHint) {
         mContext = context;
         mTransactionPool = pool;
-        mAnimType = animHint & ANIMATION_TYPE_MASK;
+        mAnimHint = animHint;
 
         mSurfaceControl = change.getLeash();
         mStartWidth = change.getStartAbsBounds().width();
@@ -177,20 +170,11 @@
                 }
                 hardwareBuffer.close();
             }
-            if ((animHint & ANIMATION_HINT_HAS_WALLPAPER) != 0) {
-                mBackEffectSurface = new SurfaceControl.Builder()
-                        .setCallsite("ShellRotationAnimation").setParent(rootLeash)
-                        .setEffectLayer().setOpaque(true).setName("BackEffect").build();
-                t.reparent(mSurfaceControl, mBackEffectSurface)
-                        .setColor(mBackEffectSurface,
-                                new float[] {mStartLuma, mStartLuma, mStartLuma})
-                        .show(mBackEffectSurface);
-            }
 
             t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
             t.show(mAnimLeash);
             // Crop the real content in case it contains a larger child layer, e.g. wallpaper.
-            t.setCrop(getEnterSurface(), new Rect(0, 0, mEndWidth, mEndHeight));
+            t.setCrop(mSurfaceControl, new Rect(0, 0, mEndWidth, mEndHeight));
 
             if (!isCustomRotate()) {
                 mBackColorSurface = new SurfaceControl.Builder()
@@ -215,12 +199,7 @@
     }
 
     private boolean isCustomRotate() {
-        return mAnimType == ROTATION_ANIMATION_CROSSFADE || mAnimType == ROTATION_ANIMATION_JUMPCUT;
-    }
-
-    /** Returns the surface which contains the real content to animate enter. */
-    private SurfaceControl getEnterSurface() {
-        return mBackEffectSurface != null ? mBackEffectSurface : mSurfaceControl;
+        return mAnimHint == ROTATION_ANIMATION_CROSSFADE || mAnimHint == ROTATION_ANIMATION_JUMPCUT;
     }
 
     private void setScreenshotTransform(SurfaceControl.Transaction t) {
@@ -281,7 +260,7 @@
         final boolean customRotate = isCustomRotate();
         if (customRotate) {
             mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                    mAnimType == ROTATION_ANIMATION_JUMPCUT ? R.anim.rotation_animation_jump_exit
+                    mAnimHint == ROTATION_ANIMATION_JUMPCUT ? R.anim.rotation_animation_jump_exit
                             : R.anim.rotation_animation_xfade_exit);
             mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
                     R.anim.rotation_animation_enter);
@@ -335,11 +314,7 @@
         } else {
             startDisplayRotation(animations, finishCallback, mainExecutor);
             startScreenshotRotationAnimation(animations, finishCallback, mainExecutor);
-            if (mBackEffectSurface != null && mStartLuma > 0.1f) {
-                // Animate from the color of background to black for smooth alpha blending.
-                buildLumaAnimation(animations, mStartLuma, 0f /* endLuma */, mBackEffectSurface,
-                        animationScale, finishCallback, mainExecutor);
-            }
+            //startColorAnimation(mTransaction, animationScale);
         }
 
         return true;
@@ -347,7 +322,7 @@
 
     private void startDisplayRotation(@NonNull ArrayList<Animator> animations,
             @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
-        buildSurfaceAnimation(animations, mRotateEnterAnimation, getEnterSurface(), finishCallback,
+        buildSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback,
                 mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
                 null /* clipRect */, false /* isActivity */);
     }
@@ -366,17 +341,40 @@
                 null /* clipRect */, false /* isActivity */);
     }
 
-    private void buildLumaAnimation(@NonNull ArrayList<Animator> animations,
-            float startLuma, float endLuma, SurfaceControl surface, float animationScale,
-            @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
-        final long durationMillis = (long) (mContext.getResources().getInteger(
-                R.integer.config_screen_rotation_color_transition) * animationScale);
-        final LumaAnimation animation = new LumaAnimation(durationMillis);
-        // Align the end with the enter animation.
-        animation.setStartOffset(mRotateEnterAnimation.getDuration() - durationMillis);
-        final LumaAnimationAdapter adapter = new LumaAnimationAdapter(surface, startLuma, endLuma);
-        buildSurfaceAnimation(animations, animation, finishCallback, mTransactionPool,
-                mainExecutor, adapter);
+    private void startColorAnimation(float animationScale, @NonNull ShellExecutor animExecutor) {
+        int colorTransitionMs = mContext.getResources().getInteger(
+                R.integer.config_screen_rotation_color_transition);
+        final float[] rgbTmpFloat = new float[3];
+        final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma);
+        final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma);
+        final long duration = colorTransitionMs * (long) animationScale;
+        final Transaction t = mTransactionPool.acquire();
+
+        final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
+        // Animation length is already expected to be scaled.
+        va.overrideDurationScale(1.0f);
+        va.setDuration(duration);
+        va.addUpdateListener(animation -> {
+            final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
+            final float fraction = currentPlayTime / va.getDuration();
+            applyColor(startColor, endColor, rgbTmpFloat, fraction, mBackColorSurface, t);
+        });
+        va.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
+                        t);
+                mTransactionPool.release(t);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
+                        t);
+                mTransactionPool.release(t);
+            }
+        });
+        animExecutor.execute(va::start);
     }
 
     public void kill() {
@@ -391,47 +389,21 @@
         if (mBackColorSurface != null && mBackColorSurface.isValid()) {
             t.remove(mBackColorSurface);
         }
-        if (mBackEffectSurface != null && mBackEffectSurface.isValid()) {
-            t.remove(mBackEffectSurface);
-        }
         t.apply();
         mTransactionPool.release(t);
     }
 
-    /** A no-op wrapper to provide animation duration. */
-    private static class LumaAnimation extends Animation {
-        LumaAnimation(long durationMillis) {
-            setDuration(durationMillis);
+    private static void applyColor(int startColor, int endColor, float[] rgbFloat,
+            float fraction, SurfaceControl surface, SurfaceControl.Transaction t) {
+        final int color = (Integer) ArgbEvaluator.getInstance().evaluate(fraction, startColor,
+                endColor);
+        Color middleColor = Color.valueOf(color);
+        rgbFloat[0] = middleColor.red();
+        rgbFloat[1] = middleColor.green();
+        rgbFloat[2] = middleColor.blue();
+        if (surface.isValid()) {
+            t.setColor(surface, rgbFloat);
         }
-    }
-
-    private static class LumaAnimationAdapter extends DefaultTransitionHandler.AnimationAdapter {
-        final float[] mColorArray = new float[3];
-        final float mStartLuma;
-        final float mEndLuma;
-        final AccelerateInterpolator mInterpolation;
-
-        LumaAnimationAdapter(@NonNull SurfaceControl leash, float startLuma, float endLuma) {
-            super(leash);
-            mStartLuma = startLuma;
-            mEndLuma = endLuma;
-            // Make the initial progress color lighter if the background is light. That avoids
-            // darker content when fading into the entering surface.
-            final float factor = Math.min(3f, (Math.max(0.5f, mStartLuma) - 0.5f) * 10);
-            Slog.d(TAG, "Luma=" + mStartLuma + " factor=" + factor);
-            mInterpolation = factor > 0.5f ? new AccelerateInterpolator(factor) : null;
-        }
-
-        @Override
-        void applyTransformation(ValueAnimator animator) {
-            final float fraction = mInterpolation != null
-                    ? mInterpolation.getInterpolation(animator.getAnimatedFraction())
-                    : animator.getAnimatedFraction();
-            final float luma = mStartLuma + fraction * (mEndLuma - mStartLuma);
-            mColorArray[0] = luma;
-            mColorArray[1] = luma;
-            mColorArray[2] = luma;
-            mTransaction.setColor(mLeash, mColorArray);
-        }
+        t.apply();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index b24c17c..23f8e6e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -610,7 +610,7 @@
                 || !Flags.enableHandleInputFix()) {
             return;
         }
-        ((AppHandleViewHolder) mWindowDecorViewHolder).disposeStatusBarInputLayer();
+        asAppHandle(mWindowDecorViewHolder).disposeStatusBarInputLayer();
     }
 
     private WindowDecorationViewHolder createViewHolder() {
@@ -647,6 +647,22 @@
         return viewHolder instanceof AppHandleViewHolder;
     }
 
+    @Nullable
+    private AppHandleViewHolder asAppHandle(WindowDecorationViewHolder viewHolder) {
+        if (viewHolder instanceof AppHandleViewHolder) {
+            return (AppHandleViewHolder) viewHolder;
+        }
+        return null;
+    }
+
+    @Nullable
+    private AppHeaderViewHolder asAppHeader(WindowDecorationViewHolder viewHolder) {
+        if (viewHolder instanceof AppHeaderViewHolder) {
+            return (AppHeaderViewHolder) viewHolder;
+        }
+        return null;
+    }
+
     @VisibleForTesting
     static void updateRelayoutParams(
             RelayoutParams relayoutParams,
@@ -1025,7 +1041,15 @@
      */
     void closeMaximizeMenu() {
         if (!isMaximizeMenuActive()) return;
-        mMaximizeMenu.close();
+        mMaximizeMenu.close(() -> {
+            // Request the accessibility service to refocus on the maximize button after closing
+            // the menu.
+            final AppHeaderViewHolder appHeader = asAppHeader(mWindowDecorViewHolder);
+            if (appHeader != null) {
+                appHeader.requestAccessibilityFocus();
+            }
+            return Unit.INSTANCE;
+        });
         mMaximizeMenu = null;
     }
 
@@ -1053,7 +1077,8 @@
         loadAppInfoIfNeeded();
         updateGenericLink();
         final boolean supportsMultiInstance = mMultiInstanceHelper
-                .supportsMultiInstanceSplit(mTaskInfo.baseActivity);
+                .supportsMultiInstanceSplit(mTaskInfo.baseActivity)
+                && Flags.enableDesktopWindowingMultiInstanceFeatures();
         final boolean shouldShowManageWindowsButton = supportsMultiInstance
                 && mMinimumInstancesFound;
         mHandleMenu = mHandleMenuFactory.create(
@@ -1407,7 +1432,7 @@
 
     void setAnimatingTaskResizeOrReposition(boolean animatingTaskResizeOrReposition) {
         if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) return;
-        ((AppHeaderViewHolder) mWindowDecorViewHolder)
+        asAppHeader(mWindowDecorViewHolder)
                 .setAnimatingTaskResizeOrReposition(animatingTaskResizeOrReposition);
     }
 
@@ -1415,16 +1440,14 @@
      * Called when there is a {@link MotionEvent#ACTION_HOVER_EXIT} on the maximize window button.
      */
     void onMaximizeButtonHoverExit() {
-        ((AppHeaderViewHolder) mWindowDecorViewHolder)
-                .onMaximizeWindowHoverExit();
+        asAppHeader(mWindowDecorViewHolder).onMaximizeWindowHoverExit();
     }
 
     /**
      * Called when there is a {@link MotionEvent#ACTION_HOVER_ENTER} on the maximize window button.
      */
     void onMaximizeButtonHoverEnter() {
-        ((AppHeaderViewHolder) mWindowDecorViewHolder)
-                .onMaximizeWindowHoverEnter();
+        asAppHeader(mWindowDecorViewHolder).onMaximizeWindowHoverEnter();
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 5b3f263..9a5b4f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -23,8 +23,6 @@
 import android.content.res.ColorStateList
 import android.content.res.Resources
 import android.graphics.Bitmap
-import android.graphics.BlendMode
-import android.graphics.BlendModeColorFilter
 import android.graphics.Point
 import android.graphics.PointF
 import android.graphics.Rect
@@ -568,9 +566,7 @@
             appIconBitmap: Bitmap?,
             appName: CharSequence?
         ) {
-            appInfoPill.background.colorFilter = BlendModeColorFilter(
-                style.backgroundColor, BlendMode.MULTIPLY
-            )
+            appInfoPill.background.setTint(style.backgroundColor)
 
             collapseMenuButton.apply {
                 imageTintList = ColorStateList.valueOf(style.textColor)
@@ -584,9 +580,7 @@
         }
 
         private fun bindWindowingPill(style: MenuStyle) {
-            windowingPill.background.colorFilter = BlendModeColorFilter(
-                style.backgroundColor, BlendMode.MULTIPLY
-            )
+            windowingPill.background.setTint(style.backgroundColor)
 
             // TODO: Remove once implemented.
             floatingBtn.visibility = View.GONE
@@ -612,23 +606,19 @@
             }
             screenshotBtn.apply {
                 isGone = !SHOULD_SHOW_SCREENSHOT_BUTTON
-                background.colorFilter =
-                    BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY
-                )
+                background.setTint(style.backgroundColor)
                 setTextColor(style.textColor)
                 compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
             }
             newWindowBtn.apply {
                 isGone = !shouldShowNewWindowButton
-                background.colorFilter =
-                    BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
+                background.setTint(style.backgroundColor)
                 setTextColor(style.textColor)
                 compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
             }
             manageWindowBtn.apply {
                 isGone = !shouldShowManageWindowsButton
-                background.colorFilter =
-                    BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
+                background.setTint(style.backgroundColor)
                 setTextColor(style.textColor)
                 compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
             }
@@ -637,9 +627,7 @@
         private fun bindOpenInBrowserPill(style: MenuStyle) {
             openInBrowserPill.apply {
                 isGone = !shouldShowBrowserPill
-                background.colorFilter = BlendModeColorFilter(
-                    style.backgroundColor, BlendMode.MULTIPLY
-                )
+                background.setTint(style.backgroundColor)
             }
 
             browserBtn.apply {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
index 9590ccd..0c475f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
@@ -26,6 +26,7 @@
 import android.view.View.TRANSLATION_Y
 import android.view.View.TRANSLATION_Z
 import android.view.ViewGroup
+import android.view.accessibility.AccessibilityEvent
 import android.widget.Button
 import androidx.core.animation.doOnEnd
 import androidx.core.view.children
@@ -83,7 +84,12 @@
         animateWindowingPillOpen()
         animateMoreActionsPillOpen()
         animateOpenInBrowserPill()
-        runAnimations()
+        runAnimations {
+            appInfoPill.post {
+                appInfoPill.requireViewById<View>(R.id.collapse_menu_button).sendAccessibilityEvent(
+                    AccessibilityEvent.TYPE_VIEW_FOCUSED)
+            }
+        }
     }
 
     /**
@@ -98,7 +104,12 @@
         animateWindowingPillOpen()
         animateMoreActionsPillOpen()
         animateOpenInBrowserPill()
-        runAnimations()
+        runAnimations {
+            appInfoPill.post {
+                appInfoPill.requireViewById<View>(R.id.collapse_menu_button).sendAccessibilityEvent(
+                    AccessibilityEvent.TYPE_VIEW_FOCUSED)
+            }
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 9c73e4a..0cb219a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -51,6 +51,7 @@
 import android.view.ViewGroup
 import android.view.WindowManager
 import android.view.WindowlessWindowManager
+import android.view.accessibility.AccessibilityEvent
 import android.widget.Button
 import android.widget.TextView
 import android.window.TaskConstants
@@ -116,19 +117,24 @@
             onHoverListener = onHoverListener,
             onOutsideTouchListener = onOutsideTouchListener
         )
-        maximizeMenuView?.animateOpenMenu()
+        maximizeMenuView?.let { view ->
+            view.animateOpenMenu(onEnd = {
+                view.requestAccessibilityFocus()
+            })
+        }
     }
 
     /** Closes the maximize window and releases its view. */
-    fun close() {
+    fun close(onEnd: () -> Unit) {
         val view = maximizeMenuView
         val menu = maximizeMenu
         if (view == null) {
             menu?.releaseView()
         } else {
-            view.animateCloseMenu {
+            view.animateCloseMenu(onEnd = {
                 menu?.releaseView()
-            }
+                onEnd.invoke()
+            })
         }
         maximizeMenu = null
         maximizeMenuView = null
@@ -351,7 +357,7 @@
         }
 
         /** Animate the opening of the menu */
-        fun animateOpenMenu() {
+        fun animateOpenMenu(onEnd: () -> Unit) {
             maximizeButton.setLayerType(View.LAYER_TYPE_HARDWARE, null)
             maximizeText.setLayerType(View.LAYER_TYPE_HARDWARE, null)
             menuAnimatorSet = AnimatorSet()
@@ -419,6 +425,7 @@
                 onEnd = {
                     maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
                     maximizeText.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
+                    onEnd.invoke()
                 }
             )
             menuAnimatorSet?.start()
@@ -499,6 +506,14 @@
             menuAnimatorSet?.start()
         }
 
+        /** Request that the accessibility service focus on the menu. */
+        fun requestAccessibilityFocus() {
+            // Focus the first button in the menu by default.
+            maximizeButton.post {
+                maximizeButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
+            }
+        }
+
         /** Cancel the menu animation. */
         private fun cancelAnimation() {
             menuAnimatorSet?.cancel()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
index af6a819..e996165 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
@@ -30,6 +30,7 @@
 import android.view.View
 import android.view.View.OnLongClickListener
 import android.view.ViewTreeObserver.OnGlobalLayoutListener
+import android.view.accessibility.AccessibilityEvent
 import android.widget.ImageButton
 import android.widget.ImageView
 import android.widget.TextView
@@ -263,7 +264,11 @@
 
     override fun onHandleMenuOpened() {}
 
-    override fun onHandleMenuClosed() {}
+    override fun onHandleMenuClosed() {
+        openMenuButton.post {
+            openMenuButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
+        }
+    }
 
     fun setAnimatingTaskResizeOrReposition(animatingTaskResizeOrReposition: Boolean) {
         // If animating a task resize or reposition, cancel any running hover animations
@@ -309,6 +314,12 @@
         )
     }
 
+    fun requestAccessibilityFocus() {
+        maximizeWindowButton.post {
+            maximizeWindowButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
+        }
+    }
+
     private fun getHeaderStyle(header: Header): HeaderStyle {
         return HeaderStyle(
             background = getHeaderBackground(header),
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt
index 717ea30..ce235d4 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt
@@ -72,7 +72,6 @@
 
     @Test
     open fun startMediaProjection() {
-        // TODO(b/366455106) - handle max task Limit
         mediaProjectionAppHelper.startSingleAppMediaProjection(wmHelper, targetApp)
         mailApp.launchViaIntent(wmHelper)
         simpleApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt
index 1573b58..f5fb4ce 100644
--- a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt
@@ -20,13 +20,12 @@
 import android.platform.test.annotations.Postsubmit
 import android.tools.NavBar
 import android.tools.Rotation
-import android.tools.flicker.rules.ChangeDisplayOrientationRule
 import android.tools.device.apphelpers.CalculatorAppHelper
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
 import android.tools.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
 import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
 import com.android.wm.shell.Utils
 import org.junit.After
@@ -47,8 +46,7 @@
 
     private val initialRotation = Rotation.ROTATION_0
     private val targetApp = CalculatorAppHelper(instrumentation)
-    private val mediaProjectionAppHelper = StartMediaProjectionAppHelper(instrumentation)
-    private val testApp = DesktopModeAppHelper(mediaProjectionAppHelper)
+    private val testApp = StartMediaProjectionAppHelper(instrumentation)
 
     @Rule
     @JvmField
@@ -63,7 +61,7 @@
 
     @Test
     open fun startMediaProjectionAndRotate() {
-        mediaProjectionAppHelper.startSingleAppMediaProjection(wmHelper, targetApp)
+        testApp.startSingleAppMediaProjection(wmHelper, targetApp)
         wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
 
         ChangeDisplayOrientationRule.setRotation(Rotation.ROTATION_90)
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt
index e80a895..28f3cc7 100644
--- a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt
@@ -25,7 +25,6 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
 import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
 import com.android.wm.shell.Utils
 import org.junit.After
@@ -45,8 +44,7 @@
     val device = UiDevice.getInstance(instrumentation)
 
     private val initialRotation = Rotation.ROTATION_0
-    private val mediaProjectionAppHelper = StartMediaProjectionAppHelper(instrumentation)
-    private val testApp = DesktopModeAppHelper(mediaProjectionAppHelper)
+    private val testApp = StartMediaProjectionAppHelper(instrumentation)
 
     @Rule
     @JvmField
@@ -60,7 +58,7 @@
 
     @Test
     open fun startMediaProjectionAndRotate() {
-        mediaProjectionAppHelper.startEntireScreenMediaProjection(wmHelper)
+        testApp.startEntireScreenMediaProjection(wmHelper)
         wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
 
         ChangeDisplayOrientationRule.setRotation(Rotation.ROTATION_90)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index e514dc3..f01ed84 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -39,6 +39,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.never;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.TaskInfo;
@@ -599,6 +600,18 @@
     }
 
     @Test
+    public void testRecentTasks_visibilityChanges_notFreeForm_shouldNotNotifyTaskController() {
+        RunningTaskInfo task1_visible = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+        mOrganizer.onTaskAppeared(task1_visible, /* leash= */ null);
+        RunningTaskInfo task1_hidden = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+        task1_hidden.isVisible = false;
+
+        mOrganizer.onTaskInfoChanged(task1_hidden);
+
+        verify(mRecentTasksController, never()).onTaskRunningInfoChanged(task1_hidden);
+    }
+
+    @Test
     public void testRecentTasks_windowingModeChanges_shouldNotifyTaskController() {
         RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
         mOrganizer.onTaskAppeared(task1, /* leash= */ null);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
index b14f163..628c9cdd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -41,12 +41,22 @@
 import com.android.wm.shell.common.TaskStackListenerImpl
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
+import com.android.wm.shell.desktopmode.persistence.Desktop
+import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertTrue
 import kotlin.test.assertNotNull
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.setMain
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -73,6 +83,7 @@
  */
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
+@ExperimentalCoroutinesApi
 @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_RESPECT_ORIENTATION_CHANGE_FOR_UNRESIZEABLE)
 class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
     @JvmField @Rule val setFlagsRule = SetFlagsRule()
@@ -82,16 +93,19 @@
     @Mock lateinit var transitions: Transitions
     @Mock lateinit var resizeTransitionHandler: ToggleResizeDesktopTaskTransitionHandler
     @Mock lateinit var taskStackListener: TaskStackListenerImpl
+    @Mock lateinit var persistentRepository: DesktopPersistentRepository
 
     private lateinit var mockitoSession: StaticMockitoSession
     private lateinit var handler: DesktopActivityOrientationChangeHandler
     private lateinit var shellInit: ShellInit
     private lateinit var taskRepository: DesktopModeTaskRepository
+    private lateinit var testScope: CoroutineScope
     // Mock running tasks are registered here so we can get the list from mock shell task organizer.
     private val runningTasks = mutableListOf<RunningTaskInfo>()
 
     @Before
     fun setUp() {
+        Dispatchers.setMain(StandardTestDispatcher())
         mockitoSession =
             mockitoSession()
                 .strictness(Strictness.LENIENT)
@@ -99,10 +113,15 @@
                 .startMocking()
         doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
 
+        testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
         shellInit = spy(ShellInit(testExecutor))
-        taskRepository = DesktopModeTaskRepository()
+        taskRepository =
+            DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope)
         whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
         whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
+        whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
+            Desktop.getDefaultInstance()
+        )
 
         handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer,
             taskStackListener, resizeTransitionHandler, taskRepository)
@@ -115,6 +134,7 @@
         mockitoSession.finishMocking()
 
         runningTasks.clear()
+        testScope.cancel()
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index d3404f7..bc40d89 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -17,27 +17,70 @@
 package com.android.wm.shell.desktopmode
 
 import android.graphics.Rect
+import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
+import android.util.ArraySet
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.Display.INVALID_DISPLAY
 import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.persistence.Desktop
+import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
+import com.android.wm.shell.sysui.ShellInit
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.fail
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.spy
+import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
+@ExperimentalCoroutinesApi
 class DesktopModeTaskRepositoryTest : ShellTestCase() {
 
     private lateinit var repo: DesktopModeTaskRepository
+    private lateinit var shellInit: ShellInit
+    private lateinit var datastoreScope: CoroutineScope
+
+    @Mock private lateinit var testExecutor: ShellExecutor
+    @Mock private lateinit var persistentRepository: DesktopPersistentRepository
 
     @Before
     fun setUp() {
-        repo = DesktopModeTaskRepository()
+        Dispatchers.setMain(StandardTestDispatcher())
+        datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
+        shellInit = spy(ShellInit(testExecutor))
+
+        repo = DesktopModeTaskRepository(context, shellInit, persistentRepository, datastoreScope)
+        whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
+            Desktop.getDefaultInstance()
+        )
+        shellInit.init()
+    }
+
+    @After
+    fun tearDown() {
+        datastoreScope.cancel()
     }
 
     @Test
@@ -455,6 +498,44 @@
     }
 
     @Test
+    @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+    fun addOrMoveFreeformTaskToTop_noTaskExists_persistenceEnabled_addsToTop() =
+        runTest(StandardTestDispatcher()) {
+            repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 5)
+            repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
+            repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 7)
+
+            val tasks = repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)
+            assertThat(tasks).containsExactly(7, 6, 5).inOrder()
+            inOrder(persistentRepository).run {
+                verify(persistentRepository)
+                    .addOrUpdateDesktop(
+                        DEFAULT_USER_ID,
+                        DEFAULT_DESKTOP_ID,
+                        visibleTasks = ArraySet(),
+                        minimizedTasks = ArraySet(),
+                        freeformTasksInZOrder = arrayListOf(5)
+                    )
+                verify(persistentRepository)
+                    .addOrUpdateDesktop(
+                        DEFAULT_USER_ID,
+                        DEFAULT_DESKTOP_ID,
+                        visibleTasks = ArraySet(),
+                        minimizedTasks = ArraySet(),
+                        freeformTasksInZOrder = arrayListOf(6, 5)
+                    )
+                verify(persistentRepository)
+                    .addOrUpdateDesktop(
+                        DEFAULT_USER_ID,
+                        DEFAULT_DESKTOP_ID,
+                        visibleTasks = ArraySet(),
+                        minimizedTasks = ArraySet(),
+                        freeformTasksInZOrder = arrayListOf(7, 6, 5)
+                    )
+            }
+    }
+
+    @Test
     fun addOrMoveFreeformTaskToTop_alreadyExists_movesToTop() {
         repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 5)
         repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
@@ -480,6 +561,55 @@
     }
 
     @Test
+    @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+    fun minimizeTask_persistenceEnabled_taskIsPersistedAsMinimized() =
+        runTest(StandardTestDispatcher()) {
+            repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 5)
+            repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
+            repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 7)
+
+            repo.minimizeTask(displayId = 0, taskId = 6)
+
+            val tasks = repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)
+            assertThat(tasks).containsExactly(7, 6, 5).inOrder()
+            assertThat(repo.isMinimizedTask(taskId = 6)).isTrue()
+            inOrder(persistentRepository).run {
+                verify(persistentRepository)
+                    .addOrUpdateDesktop(
+                        DEFAULT_USER_ID,
+                        DEFAULT_DESKTOP_ID,
+                        visibleTasks = ArraySet(),
+                        minimizedTasks = ArraySet(),
+                        freeformTasksInZOrder = arrayListOf(5)
+                    )
+                verify(persistentRepository)
+                    .addOrUpdateDesktop(
+                        DEFAULT_USER_ID,
+                        DEFAULT_DESKTOP_ID,
+                        visibleTasks = ArraySet(),
+                        minimizedTasks = ArraySet(),
+                        freeformTasksInZOrder = arrayListOf(6, 5)
+                    )
+                verify(persistentRepository)
+                    .addOrUpdateDesktop(
+                        DEFAULT_USER_ID,
+                        DEFAULT_DESKTOP_ID,
+                        visibleTasks = ArraySet(),
+                        minimizedTasks = ArraySet(),
+                        freeformTasksInZOrder = arrayListOf(7, 6, 5)
+                    )
+                verify(persistentRepository)
+                    .addOrUpdateDesktop(
+                        DEFAULT_USER_ID,
+                        DEFAULT_DESKTOP_ID,
+                        visibleTasks = ArraySet(),
+                        minimizedTasks = ArraySet(arrayOf(6)),
+                        freeformTasksInZOrder = arrayListOf(7, 6, 5)
+                    )
+            }
+    }
+
+    @Test
     fun addOrMoveFreeformTaskToTop_taskIsUnminimized_noop() {
         repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 5)
         repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
@@ -503,6 +633,33 @@
     }
 
     @Test
+    @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+    fun removeFreeformTask_invalidDisplay_persistenceEnabled_removesTaskFromFreeformTasks() {
+        runTest(StandardTestDispatcher()) {
+            repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId = 1)
+
+            repo.removeFreeformTask(INVALID_DISPLAY, taskId = 1)
+
+            verify(persistentRepository)
+                .addOrUpdateDesktop(
+                    DEFAULT_USER_ID,
+                    DEFAULT_DESKTOP_ID,
+                    visibleTasks = ArraySet(),
+                    minimizedTasks = ArraySet(),
+                    freeformTasksInZOrder = arrayListOf(1)
+                )
+            verify(persistentRepository)
+                .addOrUpdateDesktop(
+                    DEFAULT_USER_ID,
+                    DEFAULT_DESKTOP_ID,
+                    visibleTasks = ArraySet(),
+                    minimizedTasks = ArraySet(),
+                    freeformTasksInZOrder = ArrayList()
+                )
+        }
+    }
+
+    @Test
     fun removeFreeformTask_validDisplay_removesTaskFromFreeformTasks() {
         repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId = 1)
 
@@ -513,6 +670,33 @@
     }
 
     @Test
+    @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+    fun removeFreeformTask_validDisplay_persistenceEnabled_removesTaskFromFreeformTasks() {
+        runTest(StandardTestDispatcher()) {
+            repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId = 1)
+
+            repo.removeFreeformTask(DEFAULT_DISPLAY, taskId = 1)
+
+            verify(persistentRepository)
+                .addOrUpdateDesktop(
+                    DEFAULT_USER_ID,
+                    DEFAULT_DESKTOP_ID,
+                    visibleTasks = ArraySet(),
+                    minimizedTasks = ArraySet(),
+                    freeformTasksInZOrder = arrayListOf(1)
+                )
+            verify(persistentRepository)
+                .addOrUpdateDesktop(
+                    DEFAULT_USER_ID,
+                    DEFAULT_DESKTOP_ID,
+                    visibleTasks = ArraySet(),
+                    minimizedTasks = ArraySet(),
+                    freeformTasksInZOrder = ArrayList()
+                )
+        }
+    }
+
+    @Test
     fun removeFreeformTask_validDisplay_differentDisplay_doesNotRemovesTask() {
         repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId = 1)
 
@@ -523,6 +707,33 @@
     }
 
     @Test
+    @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+    fun removeFreeformTask_validDisplayButDifferentDisplay_persistenceEnabled_doesNotRemoveTask() {
+        runTest(StandardTestDispatcher()) {
+            repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId = 1)
+
+            repo.removeFreeformTask(SECOND_DISPLAY, taskId = 1)
+
+            verify(persistentRepository)
+                .addOrUpdateDesktop(
+                    DEFAULT_USER_ID,
+                    DEFAULT_DESKTOP_ID,
+                    visibleTasks = ArraySet(),
+                    minimizedTasks = ArraySet(),
+                    freeformTasksInZOrder = arrayListOf(1)
+                )
+            verify(persistentRepository, never())
+                .addOrUpdateDesktop(
+                    DEFAULT_USER_ID,
+                    DEFAULT_DESKTOP_ID,
+                    visibleTasks = ArraySet(),
+                    minimizedTasks = ArraySet(),
+                    freeformTasksInZOrder = ArrayList()
+                )
+        }
+    }
+
+    @Test
     fun removeFreeformTask_removesTaskBoundsBeforeMaximize() {
         val taskId = 1
         repo.addActiveTask(THIRD_DISPLAY, taskId)
@@ -709,5 +920,7 @@
     companion object {
         const val SECOND_DISPLAY = 1
         const val THIRD_DISPLAY = 345
+        private const val DEFAULT_USER_ID = 1000
+        private const val DEFAULT_DESKTOP_ID = 0
     }
 }
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 8f20841..ee54520 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
@@ -93,6 +93,8 @@
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask
+import com.android.wm.shell.desktopmode.persistence.Desktop
+import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
 import com.android.wm.shell.draganddrop.DragAndDropController
 import com.android.wm.shell.recents.RecentTasksController
 import com.android.wm.shell.recents.RecentsTransitionHandler
@@ -117,6 +119,14 @@
 import junit.framework.Assert.assertTrue
 import kotlin.test.assertNotNull
 import kotlin.test.assertNull
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.setMain
 import org.junit.After
 import org.junit.Assume.assumeTrue
 import org.junit.Before
@@ -148,6 +158,7 @@
  */
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
+@ExperimentalCoroutinesApi
 @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
 class DesktopTasksControllerTest : ShellTestCase() {
 
@@ -183,6 +194,7 @@
   @Mock private lateinit var mockSurface: SurfaceControl
   @Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener
   @Mock private lateinit var mockHandler: Handler
+  @Mock lateinit var persistentRepository: DesktopPersistentRepository
 
   private lateinit var mockitoSession: StaticMockitoSession
   private lateinit var controller: DesktopTasksController
@@ -190,6 +202,7 @@
   private lateinit var taskRepository: DesktopModeTaskRepository
   private lateinit var desktopTasksLimiter: DesktopTasksLimiter
   private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
+  private lateinit var testScope: CoroutineScope
 
   private val shellExecutor = TestShellExecutor()
 
@@ -207,6 +220,7 @@
 
   @Before
   fun setUp() {
+    Dispatchers.setMain(StandardTestDispatcher())
     mockitoSession =
         mockitoSession()
             .strictness(Strictness.LENIENT)
@@ -214,8 +228,9 @@
             .startMocking()
     doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
 
+    testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
     shellInit = spy(ShellInit(testExecutor))
-    taskRepository = DesktopModeTaskRepository()
+    taskRepository = DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope)
     desktopTasksLimiter =
         DesktopTasksLimiter(
             transitions,
@@ -233,6 +248,9 @@
     whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
       (i.arguments.first() as Rect).set(STABLE_BOUNDS)
     }
+    whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
+      Desktop.getDefaultInstance()
+    )
 
     val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
     tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
@@ -287,6 +305,7 @@
     mockitoSession.finishMocking()
 
     runningTasks.clear()
+    testScope.cancel()
   }
 
   @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 61d03ca..045e077 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -35,13 +35,23 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
+import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.TransitionInfoBuilder
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.util.StubTransaction
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.assertFailsWith
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.setMain
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -49,6 +59,7 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.any
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.`when`
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.verify
@@ -62,6 +73,7 @@
  */
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
+@ExperimentalCoroutinesApi
 class DesktopTasksLimiterTest : ShellTestCase() {
 
     @JvmField
@@ -72,19 +84,26 @@
     @Mock lateinit var transitions: Transitions
     @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
     @Mock lateinit var handler: Handler
+    @Mock lateinit var testExecutor: ShellExecutor
+    @Mock lateinit var persistentRepository: DesktopPersistentRepository
 
     private lateinit var mockitoSession: StaticMockitoSession
     private lateinit var desktopTasksLimiter: DesktopTasksLimiter
     private lateinit var desktopTaskRepo: DesktopModeTaskRepository
+    private lateinit var shellInit: ShellInit
+    private lateinit var testScope: CoroutineScope
 
     @Before
     fun setUp() {
         mockitoSession = ExtendedMockito.mockitoSession().strictness(Strictness.LENIENT)
                 .spyStatic(DesktopModeStatus::class.java).startMocking()
         doReturn(true).`when`{ DesktopModeStatus.canEnterDesktopMode(any()) }
+        shellInit = spy(ShellInit(testExecutor))
+        Dispatchers.setMain(StandardTestDispatcher())
+        testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
 
-        desktopTaskRepo = DesktopModeTaskRepository()
-
+        desktopTaskRepo =
+            DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope)
         desktopTasksLimiter =
             DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT,
                 interactionJankMonitor, mContext, handler)
@@ -93,6 +112,7 @@
     @After
     fun tearDown() {
         mockitoSession.finishMocking()
+        testScope.cancel()
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt
new file mode 100644
index 0000000..9b9703f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt
@@ -0,0 +1,198 @@
+/*
+ * 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.desktopmode.persistence
+
+import android.content.Context
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.util.ArraySet
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.datastore.core.DataStore
+import androidx.datastore.core.DataStoreFactory
+import androidx.datastore.dataStoreFile
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE
+import com.android.wm.shell.ShellTestCase
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@ExperimentalCoroutinesApi
+@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+class DesktopPersistentRepositoryTest : ShellTestCase() {
+    private val testContext: Context = InstrumentationRegistry.getInstrumentation().targetContext
+    private lateinit var testDatastore: DataStore<DesktopPersistentRepositories>
+    private lateinit var datastoreRepository: DesktopPersistentRepository
+    private lateinit var datastoreScope: CoroutineScope
+
+    @Before
+    fun setUp() {
+        Dispatchers.setMain(StandardTestDispatcher())
+        datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
+        testDatastore =
+            DataStoreFactory.create(
+                serializer =
+                    DesktopPersistentRepository.Companion.DesktopPersistentRepositoriesSerializer,
+                scope = datastoreScope) {
+                    testContext.dataStoreFile(DESKTOP_REPOSITORY_STATES_DATASTORE_TEST_FILE)
+                }
+        datastoreRepository = DesktopPersistentRepository(testDatastore)
+    }
+
+    @After
+    fun tearDown() {
+        File(ApplicationProvider.getApplicationContext<Context>().filesDir, "datastore")
+            .deleteRecursively()
+
+        datastoreScope.cancel()
+    }
+
+    @Test
+    fun readRepository_returnsCorrectDesktop() {
+        runTest(StandardTestDispatcher()) {
+            val task = createDesktopTask(1)
+            val desk = createDesktop(task)
+            val repositoryState =
+                DesktopRepositoryState.newBuilder().putDesktop(DEFAULT_DESKTOP_ID, desk)
+            val DesktopPersistentRepositories =
+                DesktopPersistentRepositories.newBuilder()
+                    .putDesktopRepoByUser(DEFAULT_USER_ID, repositoryState.build())
+                    .build()
+            testDatastore.updateData { DesktopPersistentRepositories }
+
+            val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
+
+            assertThat(actualDesktop).isEqualTo(desk)
+        }
+    }
+
+    @Test
+    fun addOrUpdateTask_addNewTaskToDesktop() {
+        runTest(StandardTestDispatcher()) {
+            // Create a basic repository state
+            val task = createDesktopTask(1)
+            val DesktopPersistentRepositories = createRepositoryWithOneDesk(task)
+            testDatastore.updateData { DesktopPersistentRepositories }
+            // Create a new state to be initialized
+            val visibleTasks = ArraySet(listOf(1, 2))
+            val minimizedTasks = ArraySet<Int>()
+            val freeformTasksInZOrder = ArrayList(listOf(2, 1))
+
+            // Update with new state
+            datastoreRepository.addOrUpdateDesktop(
+                visibleTasks = visibleTasks,
+                minimizedTasks = minimizedTasks,
+                freeformTasksInZOrder = freeformTasksInZOrder)
+
+            val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
+            assertThat(actualDesktop.tasksByTaskIdMap).hasSize(2)
+            assertThat(actualDesktop.getZOrderedTasks(0)).isEqualTo(2)
+        }
+    }
+
+    @Test
+    fun addOrUpdateTask_changeTaskStateToMinimize_taskStateIsMinimized() {
+        runTest(StandardTestDispatcher()) {
+            val task = createDesktopTask(1)
+            val DesktopPersistentRepositories = createRepositoryWithOneDesk(task)
+            testDatastore.updateData { DesktopPersistentRepositories }
+            // Create a new state to be initialized
+            val visibleTasks = ArraySet(listOf(1))
+            val minimizedTasks = ArraySet(listOf(1))
+            val freeformTasksInZOrder = ArrayList(listOf(1))
+
+            // Update with new state
+            datastoreRepository.addOrUpdateDesktop(
+                visibleTasks = visibleTasks,
+                minimizedTasks = minimizedTasks,
+                freeformTasksInZOrder = freeformTasksInZOrder)
+
+            val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
+            assertThat(actualDesktop.tasksByTaskIdMap[task.taskId]?.desktopTaskState)
+                .isEqualTo(DesktopTaskState.MINIMIZED)
+        }
+    }
+
+    @Test
+    fun removeTask_previouslyAddedTaskIsRemoved() {
+        runTest(StandardTestDispatcher()) {
+            val task = createDesktopTask(1)
+            val DesktopPersistentRepositories = createRepositoryWithOneDesk(task)
+            testDatastore.updateData { DesktopPersistentRepositories }
+            // Create a new state to be initialized
+            val visibleTasks = ArraySet<Int>()
+            val minimizedTasks = ArraySet<Int>()
+            val freeformTasksInZOrder = ArrayList<Int>()
+
+            // Update with new state
+            datastoreRepository.addOrUpdateDesktop(
+                visibleTasks = visibleTasks,
+                minimizedTasks = minimizedTasks,
+                freeformTasksInZOrder = freeformTasksInZOrder)
+
+            val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
+            assertThat(actualDesktop.tasksByTaskIdMap).isEmpty()
+            assertThat(actualDesktop.zOrderedTasksList).isEmpty()
+        }
+    }
+
+    private companion object {
+        const val DESKTOP_REPOSITORY_STATES_DATASTORE_TEST_FILE = "desktop_repo_test.pb"
+        const val DEFAULT_USER_ID = 1000
+        const val DEFAULT_DESKTOP_ID = 0
+
+        fun createRepositoryWithOneDesk(task: DesktopTask): DesktopPersistentRepositories {
+            val desk = createDesktop(task)
+            val repositoryState =
+                DesktopRepositoryState.newBuilder().putDesktop(DEFAULT_DESKTOP_ID, desk)
+            val DesktopPersistentRepositories =
+                DesktopPersistentRepositories.newBuilder()
+                    .putDesktopRepoByUser(DEFAULT_USER_ID, repositoryState.build())
+                    .build()
+            return DesktopPersistentRepositories
+        }
+
+        fun createDesktop(task: DesktopTask): Desktop? =
+            Desktop.newBuilder()
+                .setDisplayId(DEFAULT_DISPLAY)
+                .addZOrderedTasks(task.taskId)
+                .putTasksByTaskId(task.taskId, task)
+                .build()
+
+        fun createDesktopTask(
+            taskId: Int,
+            state: DesktopTaskState = DesktopTaskState.VISIBLE
+        ): DesktopTask =
+            DesktopTask.newBuilder().setTaskId(taskId).setDesktopTaskState(state).build()
+    }
+}
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 a1867f3..9c11ec3 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
@@ -623,7 +623,7 @@
                 .postDelayed(mCloseMaxMenuRunnable.capture(), eq(CLOSE_MAXIMIZE_MENU_DELAY_MS));
 
         mCloseMaxMenuRunnable.getValue().run();
-        verify(menu).close();
+        verify(menu).close(any());
         assertFalse(decoration.isMaximizeMenuActive());
     }
 
@@ -642,7 +642,7 @@
                 .postDelayed(mCloseMaxMenuRunnable.capture(), eq(CLOSE_MAXIMIZE_MENU_DELAY_MS));
 
         mCloseMaxMenuRunnable.getValue().run();
-        verify(menu).close();
+        verify(menu).close(any());
         assertFalse(decoration.isMaximizeMenuActive());
     }
 
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 589abb4..2c23864 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -404,13 +404,19 @@
     }
 }
 
+inline bool RenderNode::isForceInvertDark(TreeInfo& info) {
+    return CC_UNLIKELY(
+             info.forceDarkType == android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK);
+}
+
 inline bool RenderNode::shouldEnableForceDark(TreeInfo* info) {
     return CC_UNLIKELY(
             info &&
-            (!info->disableForceDark ||
-             info->forceDarkType == android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK));
+            (!info->disableForceDark || isForceInvertDark(*info)));
 }
 
+
+
 void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) {
     if (!shouldEnableForceDark(info)) {
         return;
@@ -421,7 +427,7 @@
         children.push_back(node);
     });
     if (mDisplayList.hasText()) {
-        if (mDisplayList.hasFill()) {
+        if (isForceInvertDark(*info) && mDisplayList.hasFill()) {
             // Handle a special case for custom views that draw both text and background in the
             // same RenderNode, which would otherwise be altered to white-on-white text.
             usage = UsageHint::Container;
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index c904542..afbbce7 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -234,6 +234,7 @@
     void syncDisplayList(TreeObserver& observer, TreeInfo* info);
     void handleForceDark(TreeInfo* info);
     bool shouldEnableForceDark(TreeInfo* info);
+    bool isForceInvertDark(TreeInfo& info);
 
     void prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer);
     void pushStagingPropertiesChanges(TreeInfo& info);
diff --git a/libs/hwui/jni/GraphicsStatsService.cpp b/libs/hwui/jni/GraphicsStatsService.cpp
index 54369b9..80a8ae1 100644
--- a/libs/hwui/jni/GraphicsStatsService.cpp
+++ b/libs/hwui/jni/GraphicsStatsService.cpp
@@ -42,8 +42,9 @@
     return reinterpret_cast<jlong>(dump);
 }
 
-static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jstring jpackage,
-                      jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
+static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jint uid,
+                      jstring jpackage, jlong versionCode, jlong startTime, jlong endTime,
+                      jbyteArray jdata) {
     std::string path;
     const ProfileData* data = nullptr;
     LOG_ALWAYS_FATAL_IF(jdata == nullptr && jpath == nullptr, "Path and data can't both be null");
@@ -68,7 +69,8 @@
     LOG_ALWAYS_FATAL_IF(!dump, "null passed for dump pointer");
 
     const std::string package(packageChars.c_str(), packageChars.size());
-    GraphicsStatsService::addToDump(dump, path, package, versionCode, startTime, endTime, data);
+    GraphicsStatsService::addToDump(dump, path, static_cast<uid_t>(uid), package, versionCode,
+                                    startTime, endTime, data);
 }
 
 static void addFileToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath) {
@@ -91,7 +93,7 @@
     GraphicsStatsService::finishDumpInMemory(dump, data, lastFullDay == JNI_TRUE);
 }
 
-static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage,
+static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jint uid, jstring jpackage,
                        jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
     ScopedByteArrayRO buffer(env, jdata);
     LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData),
@@ -106,7 +108,8 @@
     const std::string path(pathChars.c_str(), pathChars.size());
     const std::string package(packageChars.c_str(), packageChars.size());
     const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer.get());
-    GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data);
+    GraphicsStatsService::saveBuffer(path, static_cast<uid_t>(uid), package, versionCode, startTime,
+                                     endTime, data);
 }
 
 static jobject gGraphicsStatsServiceObject = nullptr;
@@ -173,16 +176,16 @@
 } // namespace android
 using namespace android;
 
-static const JNINativeMethod sMethods[] =
-        {{"nGetAshmemSize", "()I", (void*)getAshmemSize},
-         {"nCreateDump", "(IZ)J", (void*)createDump},
-         {"nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*)addToDump},
-         {"nAddToDump", "(JLjava/lang/String;)V", (void*)addFileToDump},
-         {"nFinishDump", "(J)V", (void*)finishDump},
-         {"nFinishDumpInMemory", "(JJZ)V", (void*)finishDumpInMemory},
-         {"nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*)saveBuffer},
-         {"nativeInit", "()V", (void*)nativeInit},
-         {"nativeDestructor", "()V", (void*)nativeDestructor}};
+static const JNINativeMethod sMethods[] = {
+        {"nGetAshmemSize", "()I", (void*)getAshmemSize},
+        {"nCreateDump", "(IZ)J", (void*)createDump},
+        {"nAddToDump", "(JLjava/lang/String;ILjava/lang/String;JJJ[B)V", (void*)addToDump},
+        {"nAddToDump", "(JLjava/lang/String;)V", (void*)addFileToDump},
+        {"nFinishDump", "(J)V", (void*)finishDump},
+        {"nFinishDumpInMemory", "(JJZ)V", (void*)finishDumpInMemory},
+        {"nSaveBuffer", "(Ljava/lang/String;ILjava/lang/String;JJJ[B)V", (void*)saveBuffer},
+        {"nativeInit", "()V", (void*)nativeInit},
+        {"nativeDestructor", "()V", (void*)nativeDestructor}};
 
 int register_android_graphics_GraphicsStatsService(JNIEnv* env) {
     jclass graphicsStatsService_class =
diff --git a/libs/hwui/protos/graphicsstats.proto b/libs/hwui/protos/graphicsstats.proto
index 745393c..a6e786c 100644
--- a/libs/hwui/protos/graphicsstats.proto
+++ b/libs/hwui/protos/graphicsstats.proto
@@ -58,6 +58,9 @@
 
     // HWUI renders pipeline type: GL or Vulkan
     optional PipelineType pipeline = 8;
+
+    // The UID of the app
+    optional int32 uid = 9;
 }
 
 message GraphicsStatsJankSummaryProto {
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index ece5905..702f2a5 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -22,6 +22,7 @@
 #include <google/protobuf/io/zero_copy_stream_impl_lite.h>
 #include <inttypes.h>
 #include <log/log.h>
+#include <stats_annotations.h>
 #include <stats_event.h>
 #include <statslog_hwui.h>
 #include <sys/mman.h>
@@ -45,9 +46,9 @@
 constexpr int sHistogramSize = ProfileData::HistogramSize();
 constexpr int sGPUHistogramSize = ProfileData::GPUHistogramSize();
 
-static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package,
-                                      int64_t versionCode, int64_t startTime, int64_t endTime,
-                                      const ProfileData* data);
+static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, uid_t uid,
+                                      const std::string& package, int64_t versionCode,
+                                      int64_t startTime, int64_t endTime, const ProfileData* data);
 static void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int outFd);
 
 class FileDescriptor {
@@ -159,15 +160,16 @@
     return success;
 }
 
-bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package,
-                               int64_t versionCode, int64_t startTime, int64_t endTime,
-                               const ProfileData* data) {
+bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, uid_t uid,
+                               const std::string& package, int64_t versionCode, int64_t startTime,
+                               int64_t endTime, const ProfileData* data) {
     if (proto->stats_start() == 0 || proto->stats_start() > startTime) {
         proto->set_stats_start(startTime);
     }
     if (proto->stats_end() == 0 || proto->stats_end() < endTime) {
         proto->set_stats_end(endTime);
     }
+    proto->set_uid(static_cast<int32_t>(uid));
     proto->set_package_name(package);
     proto->set_version_code(versionCode);
     proto->set_pipeline(data->pipelineType() == RenderPipelineType::SkiaGL ?
@@ -286,6 +288,7 @@
               proto->package_name().c_str(), proto->has_summary());
         return;
     }
+    dprintf(fd, "\nUID: %d", proto->uid());
     dprintf(fd, "\nPackage: %s", proto->package_name().c_str());
     dprintf(fd, "\nVersion: %" PRId64, proto->version_code());
     dprintf(fd, "\nStats since: %" PRId64 "ns", proto->stats_start());
@@ -319,14 +322,15 @@
     dprintf(fd, "\n");
 }
 
-void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package,
-                                      int64_t versionCode, int64_t startTime, int64_t endTime,
-                                      const ProfileData* data) {
+void GraphicsStatsService::saveBuffer(const std::string& path, uid_t uid,
+                                      const std::string& package, int64_t versionCode,
+                                      int64_t startTime, int64_t endTime, const ProfileData* data) {
     protos::GraphicsStatsProto statsProto;
     if (!parseFromFile(path, &statsProto)) {
         statsProto.Clear();
     }
-    if (!mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) {
+    if (!mergeProfileDataIntoProto(&statsProto, uid, package, versionCode, startTime, endTime,
+                                   data)) {
         return;
     }
     // Although we might not have read any data from the file, merging the existing data
@@ -383,7 +387,7 @@
 
 private:
     // use package name and app version for a key
-    typedef std::pair<std::string, int64_t> DumpKey;
+    typedef std::tuple<uid_t, std::string, int64_t> DumpKey;
 
     std::map<DumpKey, protos::GraphicsStatsProto> mStats;
     int mFd;
@@ -392,7 +396,8 @@
 };
 
 void GraphicsStatsService::Dump::mergeStat(const protos::GraphicsStatsProto& stat) {
-    auto dumpKey = std::make_pair(stat.package_name(), stat.version_code());
+    auto dumpKey = std::make_tuple(static_cast<uid_t>(stat.uid()), stat.package_name(),
+                                   stat.version_code());
     auto findIt = mStats.find(dumpKey);
     if (findIt == mStats.end()) {
         mStats[dumpKey] = stat;
@@ -437,15 +442,15 @@
     return new Dump(outFd, type);
 }
 
-void GraphicsStatsService::addToDump(Dump* dump, const std::string& path,
+void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, uid_t uid,
                                      const std::string& package, int64_t versionCode,
                                      int64_t startTime, int64_t endTime, const ProfileData* data) {
     protos::GraphicsStatsProto statsProto;
     if (!path.empty() && !parseFromFile(path, &statsProto)) {
         statsProto.Clear();
     }
-    if (data &&
-        !mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) {
+    if (data && !mergeProfileDataIntoProto(&statsProto, uid, package, versionCode, startTime,
+                                           endTime, data)) {
         return;
     }
     if (!statsProto.IsInitialized()) {
@@ -556,6 +561,8 @@
         // TODO: fill in UI mainline module version, when the feature is available.
         AStatsEvent_writeInt64(event, (int64_t)0);
         AStatsEvent_writeBool(event, !lastFullDay);
+        AStatsEvent_writeInt32(event, stat.uid());
+        AStatsEvent_addBoolAnnotation(event, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
         AStatsEvent_build(event);
     }
     delete dump;
diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h
index 4063f74..68c7355 100644
--- a/libs/hwui/service/GraphicsStatsService.h
+++ b/libs/hwui/service/GraphicsStatsService.h
@@ -44,13 +44,14 @@
         ProtobufStatsd,
     };
 
-    static void saveBuffer(const std::string& path, const std::string& package, int64_t versionCode,
-                           int64_t startTime, int64_t endTime, const ProfileData* data);
+    static void saveBuffer(const std::string& path, uid_t uid, const std::string& package,
+                           int64_t versionCode, int64_t startTime, int64_t endTime,
+                           const ProfileData* data);
 
     static Dump* createDump(int outFd, DumpType type);
-    static void addToDump(Dump* dump, const std::string& path, const std::string& package,
-                          int64_t versionCode, int64_t startTime, int64_t endTime,
-                          const ProfileData* data);
+    static void addToDump(Dump* dump, const std::string& path, uid_t uid,
+                          const std::string& package, int64_t versionCode, int64_t startTime,
+                          int64_t endTime, const ProfileData* data);
     static void addToDump(Dump* dump, const std::string& path);
     static void finishDump(Dump* dump);
     static void finishDumpInMemory(Dump* dump, AStatsEventList* data, bool lastFullDay);
diff --git a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
index c2d23e6..eb164f9 100644
--- a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
+++ b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
@@ -62,6 +62,7 @@
 
 TEST(GraphicsStats, saveLoad) {
     std::string path = findRootPath() + "/test_saveLoad";
+    uid_t uid = 123;
     std::string packageName = "com.test.saveLoad";
     MockProfileData mockData;
     mockData.editJankFrameCount() = 20;
@@ -75,12 +76,13 @@
     for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) {
         mockData.editSlowFrameCounts()[i] = (i % 5) + 1;
     }
-    GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData);
+    GraphicsStatsService::saveBuffer(path, uid, packageName, 5, 3000, 7000, &mockData);
     protos::GraphicsStatsProto loadedProto;
     EXPECT_TRUE(GraphicsStatsService::parseFromFile(path, &loadedProto));
     // Clean up the file
     unlink(path.c_str());
 
+    EXPECT_EQ(uid, loadedProto.uid());
     EXPECT_EQ(packageName, loadedProto.package_name());
     EXPECT_EQ(5, loadedProto.version_code());
     EXPECT_EQ(3000, loadedProto.stats_start());
@@ -109,6 +111,7 @@
 TEST(GraphicsStats, merge) {
     std::string path = findRootPath() + "/test_merge";
     std::string packageName = "com.test.merge";
+    uid_t uid = 123;
     MockProfileData mockData;
     mockData.editJankFrameCount() = 20;
     mockData.editTotalFrameCount() = 100;
@@ -121,7 +124,7 @@
     for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) {
         mockData.editSlowFrameCounts()[i] = (i % 5) + 1;
     }
-    GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData);
+    GraphicsStatsService::saveBuffer(path, uid, packageName, 5, 3000, 7000, &mockData);
     mockData.editJankFrameCount() = 50;
     mockData.editTotalFrameCount() = 500;
     for (size_t i = 0; i < mockData.editFrameCounts().size(); i++) {
@@ -130,13 +133,15 @@
     for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) {
         mockData.editSlowFrameCounts()[i] = ((i % 10) + 1) * 2;
     }
-    GraphicsStatsService::saveBuffer(path, packageName, 5, 7050, 10000, &mockData);
+
+    GraphicsStatsService::saveBuffer(path, uid, packageName, 5, 7050, 10000, &mockData);
 
     protos::GraphicsStatsProto loadedProto;
     EXPECT_TRUE(GraphicsStatsService::parseFromFile(path, &loadedProto));
     // Clean up the file
     unlink(path.c_str());
 
+    EXPECT_EQ(uid, loadedProto.uid());
     EXPECT_EQ(packageName, loadedProto.package_name());
     EXPECT_EQ(5, loadedProto.version_code());
     EXPECT_EQ(3000, loadedProto.stats_start());
diff --git a/media/java/android/media/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig
index bf6ec96..185f579 100644
--- a/media/java/android/media/flags/editing.aconfig
+++ b/media/java/android/media/flags/editing.aconfig
@@ -8,3 +8,10 @@
   description: "Add media metrics for transcoding/editing events."
   bug: "297487694"
 }
+
+flag {
+  name: "stagefrightrecorder_enable_b_frames"
+  namespace: "media_solutions"
+  description: "Enable B frames for Stagefright recorder."
+  bug: "341121900"
+}
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml
new file mode 100644
index 0000000..16ca18a
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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="18dp"
+    android:height="18dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="@color/settingslib_materialColorOnSurfaceVariant"
+    android:autoMirrored="true">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M321,880L250,809L579,480L250,151L321,80L721,480L321,880Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml
new file mode 100644
index 0000000..3f75181
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/two_target_divider"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:gravity="start|center_vertical"
+    android:orientation="horizontal"
+    android:paddingStart="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingLeft="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall7">
+
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingEnd="@dimen/settingslib_expressive_space_extrasmall6"
+        android:src="@drawable/settingslib_expressive_icon_chevron"/>
+
+    <View
+        android:layout_width="1dp"
+        android:layout_height="40dp"
+        android:background="?android:attr/listDivider" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 3011ce0..b69912a 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
 
 allprojects {
     extra["androidTop"] = androidTop
-    extra["jetpackComposeVersion"] = "1.7.0-rc01"
+    extra["jetpackComposeVersion"] = "1.7.0"
 }
 
 subprojects {
diff --git a/packages/SettingsLib/Spa/gallery/res/values/strings.xml b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
index 18a6db0..f942fd0 100644
--- a/packages/SettingsLib/Spa/gallery/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
@@ -26,6 +26,8 @@
     <string name="single_line_summary_preference_summary" translatable="false">A very long summary to show case a preference which only shows a single line summary.</string>
     <!-- Footer text with two links. [DO NOT TRANSLATE] -->
     <string name="footer_with_two_links" translatable="false">Annotated string with <a href="https://www.android.com/">link 1</a> and <a href="https://source.android.com/">link 2</a>.</string>
+    <!-- TopIntroPreference preview text. [DO NOT TRANSLATE] -->
+    <string name="label_with_two_links" translatable="false"><a href="https://www.android.com/">Label</a></string>
 
     <!-- Sample title -->
     <string name="sample_title" translatable="false">Lorem ipsum</string>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 83d657e..7139f5b4 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -42,12 +42,15 @@
 import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
 import com.android.settingslib.spa.gallery.scaffold.NonScrollablePagerPageProvider
 import com.android.settingslib.spa.gallery.page.SliderPageProvider
+import com.android.settingslib.spa.gallery.preference.IntroPreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.ListPreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.MainSwitchPreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
 import com.android.settingslib.spa.gallery.preference.PreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider
+import com.android.settingslib.spa.gallery.preference.TopIntroPreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider
+import com.android.settingslib.spa.gallery.preference.ZeroStatePreferencePageProvider
 import com.android.settingslib.spa.gallery.scaffold.PagerMainPageProvider
 import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider
 import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider
@@ -82,6 +85,7 @@
                 MainSwitchPreferencePageProvider,
                 ListPreferencePageProvider,
                 TwoTargetSwitchPreferencePageProvider,
+                ZeroStatePreferencePageProvider,
                 ArgumentPageProvider,
                 SliderPageProvider,
                 SpinnerPageProvider,
@@ -109,6 +113,8 @@
                 SuwScaffoldPageProvider,
                 BannerPageProvider,
                 CopyablePageProvider,
+                IntroPreferencePageProvider,
+                TopIntroPreferencePageProvider,
             ),
             rootPages = listOf(
                 HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
new file mode 100644
index 0000000..603fcee
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AirplanemodeActive
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.preference.IntroPreference
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+
+private const val TITLE = "Sample IntroPreference"
+
+object IntroPreferencePageProvider : SettingsPageProvider {
+    override val name = "IntroPreference"
+    private val owner = createSettingsPage()
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val entryList = mutableListOf<SettingsEntry>()
+        entryList.add(
+            SettingsEntryBuilder.create("IntroPreference", owner)
+                .setUiLayoutFn { SampleIntroPreference() }
+                .build()
+        )
+
+        return entryList
+    }
+
+    fun buildInjectEntry(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
+            Preference(
+                object : PreferenceModel {
+                    override val title = TITLE
+                    override val onClick = navigator(name)
+                }
+            )
+        }
+    }
+
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
+}
+
+@Composable
+private fun SampleIntroPreference() {
+    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+        IntroPreference(
+            title = "Preferred network type",
+            descriptions = listOf("Description"),
+            imageVector = Icons.Outlined.AirplanemodeActive,
+        )
+    }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
index ce9678b..1626b02 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
@@ -39,6 +39,9 @@
             ListPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             TwoTargetSwitchPreferencePageProvider.buildInjectEntry()
                 .setLink(fromPage = owner).build(),
+            ZeroStatePreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            IntroPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            TopIntroPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
         )
     }
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt
new file mode 100644
index 0000000..b251266
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.android.settingslib.spa.gallery.R
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.TopIntroPreference
+import com.android.settingslib.spa.widget.preference.TopIntroPreferenceModel
+
+private const val TITLE = "Sample TopIntroPreference"
+
+object TopIntroPreferencePageProvider : SettingsPageProvider {
+    override val name = "TopIntroPreference"
+    private val owner = createSettingsPage()
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val entryList = mutableListOf<SettingsEntry>()
+        entryList.add(
+            SettingsEntryBuilder.create("TopIntroPreference", owner)
+                .setUiLayoutFn { SampleTopIntroPreference() }
+                .build()
+        )
+
+        return entryList
+    }
+
+    fun buildInjectEntry(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
+            Preference(
+                object : PreferenceModel {
+                    override val title = TITLE
+                    override val onClick = navigator(name)
+                }
+            )
+        }
+    }
+
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
+}
+
+@Composable
+private fun SampleTopIntroPreference() {
+    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+        TopIntroPreference(
+            object : TopIntroPreferenceModel {
+                override val text =
+                    "Additional text needed for the page. This can sit on the right side of the screen in 2 column.\n" +
+                        "Example collapsed text area that you will not see until you expand this block."
+                override val expandText = "Expand"
+                override val collapseText = "Collapse"
+                override val labelText = R.string.label_with_two_links
+            }
+        )
+    }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt
new file mode 100644
index 0000000..4a9c5c8
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.History
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.ZeroStatePreference
+
+private const val TITLE = "Sample ZeroStatePreference"
+
+object ZeroStatePreferencePageProvider : SettingsPageProvider {
+    override val name = "ZeroStatePreference"
+    private val owner = createSettingsPage()
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val entryList = mutableListOf<SettingsEntry>()
+        entryList.add(
+            SettingsEntryBuilder.create("ZeroStatePreference", owner)
+                .setUiLayoutFn {
+                    SampleZeroStatePreference()
+                }.build()
+        )
+
+        return entryList
+    }
+
+    fun buildInjectEntry(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(owner)
+            .setUiLayoutFn {
+                Preference(object : PreferenceModel {
+                    override val title = TITLE
+                    override val onClick = navigator(name)
+                })
+            }
+    }
+
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
+}
+
+@Composable
+private fun SampleZeroStatePreference() {
+    Box(
+        modifier = Modifier.fillMaxSize(),
+        contentAlignment = Alignment.Center
+    ) {
+        ZeroStatePreference(
+            Icons.Filled.History,
+            "No recent search history",
+            "Description"
+        )
+    }
+}
+
+
+@Preview(showBackground = true)
+@Composable
+private fun SwitchPreferencePagePreview() {
+    SettingsTheme {
+        ZeroStatePreferencePageProvider.Page(null)
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index f0c2ea6..790aa9f 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -54,15 +54,16 @@
 dependencies {
     api(project(":SettingsLibColor"))
     api("androidx.appcompat:appcompat:1.7.0")
-    api("androidx.compose.material3:material3:1.3.0-rc01")
+    api("androidx.compose.material3:material3:1.3.0")
     api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
     api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
     api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
     api("androidx.lifecycle:lifecycle-livedata-ktx")
     api("androidx.lifecycle:lifecycle-runtime-compose")
-    api("androidx.navigation:navigation-compose:2.8.0-rc01")
+    api("androidx.navigation:navigation-compose:2.8.1")
     api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
     api("com.google.android.material:material:1.11.0")
+    api("androidx.graphics:graphics-shapes-android:1.0.1")
     debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
     implementation("com.airbnb.android:lottie-compose:6.4.0")
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 1f3e2425..f8c791a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -21,7 +21,9 @@
 
 object SettingsDimension {
     val paddingTiny = 2.dp
-    val paddingSmall = 4.dp
+    val paddingExtraSmall = 4.dp
+    val paddingSmall = if (isSpaExpressiveEnabled) 8.dp else 4.dp
+    val paddingExtraSmall5 = 10.dp
     val paddingLarge = 16.dp
     val paddingExtraLarge = 24.dp
 
@@ -56,6 +58,7 @@
     val itemDividerHeight = 32.dp
 
     val iconLarge = 48.dp
+    val introIconSize = 40.dp
 
     /** The size when app icon is displayed in list. */
     val appIconItemSize = 32.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
index 15def72..f948d51 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
@@ -21,6 +21,7 @@
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import com.android.settingslib.spa.framework.util.SystemProperties
 
 /**
  * The Material 3 Theme for Settings.
@@ -41,4 +42,5 @@
     }
 }
 
-const val isSpaExpressiveEnabled = false
\ No newline at end of file
+val isSpaExpressiveEnabled
+    by lazy { SystemProperties.getBoolean("is_expressive_design_enabled", false) }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SystemProperties.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SystemProperties.kt
new file mode 100644
index 0000000..ed4936b
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SystemProperties.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import android.annotation.SuppressLint
+import android.util.Log
+
+@SuppressLint("PrivateApi")
+object SystemProperties {
+    private const val TAG = "SystemProperties"
+
+    fun getBoolean(key: String, default: Boolean): Boolean = try {
+        val systemProperties = Class.forName("android.os.SystemProperties")
+        systemProperties
+            .getMethod("getBoolean", String::class.java, Boolean::class.java)
+            .invoke(systemProperties, key, default) as Boolean
+    } catch (e: Exception) {
+        Log.e(TAG, "getBoolean: $key", e)
+        default
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
new file mode 100644
index 0000000..22a5755
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.spa.widget.preference
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AirplanemodeActive
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+
+@Composable
+fun IntroPreference(
+    title: String,
+    descriptions: List<String>? = null,
+    imageVector: ImageVector? = null,
+) {
+    IntroPreference(title = title, descriptions = descriptions, icon = { IntroIcon(imageVector) })
+}
+
+@Composable
+fun IntroAppPreference(
+    title: String,
+    descriptions: List<String>? = null,
+    appIcon: @Composable (() -> Unit),
+) {
+    IntroPreference(title = title, descriptions = descriptions, icon = { IntroAppIcon(appIcon) })
+}
+
+@Composable
+internal fun IntroPreference(
+    title: String,
+    descriptions: List<String>?,
+    icon: @Composable (() -> Unit),
+) {
+    Column(
+        modifier =
+            Modifier.fillMaxWidth()
+                .padding(
+                    horizontal = SettingsDimension.paddingExtraLarge,
+                    vertical = SettingsDimension.paddingLarge,
+                ),
+        horizontalAlignment = Alignment.CenterHorizontally,
+    ) {
+        icon()
+        IntroTitle(title)
+        IntroDescription(descriptions)
+    }
+}
+
+@Composable
+private fun IntroIcon(imageVector: ImageVector?) {
+    if (imageVector != null) {
+        Box(
+            modifier =
+                Modifier.size(SettingsDimension.itemIconContainerSize)
+                    .clip(CircleShape)
+                    .background(MaterialTheme.colorScheme.secondaryContainer),
+            contentAlignment = Alignment.Center,
+        ) {
+            Icon(
+                imageVector = imageVector,
+                contentDescription = null,
+                modifier = Modifier.size(SettingsDimension.introIconSize),
+                tint = MaterialTheme.colorScheme.onSecondary,
+            )
+        }
+    }
+}
+
+@Composable
+private fun IntroAppIcon(appIcon: @Composable () -> Unit) {
+    Box(
+        modifier = Modifier.size(SettingsDimension.itemIconContainerSize).clip(CircleShape),
+        contentAlignment = Alignment.Center,
+    ) {
+        appIcon()
+    }
+}
+
+@Composable
+private fun IntroTitle(title: String) {
+    Box(modifier = Modifier.padding(top = SettingsDimension.paddingLarge)) {
+        Text(
+            text = title,
+            textAlign = TextAlign.Center,
+            style = MaterialTheme.typography.titleLarge,
+            color = MaterialTheme.colorScheme.onSurface,
+        )
+    }
+}
+
+@Composable
+private fun IntroDescription(descriptions: List<String>?) {
+    if (descriptions != null) {
+        for (description in descriptions) {
+            if (description.isEmpty()) continue
+            Text(
+                text = description,
+                textAlign = TextAlign.Center,
+                style = MaterialTheme.typography.titleMedium,
+                color = MaterialTheme.colorScheme.onSurfaceVariant,
+                modifier = Modifier.padding(top = SettingsDimension.paddingExtraSmall),
+            )
+        }
+    }
+}
+
+@Preview
+@Composable
+private fun IntroPreferencePreview() {
+    IntroPreference(
+        title = "Preferred network type",
+        descriptions = listOf("Description", "Version"),
+        imageVector = Icons.Outlined.AirplanemodeActive,
+    )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TopIntroPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TopIntroPreference.kt
new file mode 100644
index 0000000..7e61959
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TopIntroPreference.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.spa.widget.preference
+
+import androidx.annotation.StringRes
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.KeyboardArrowDown
+import androidx.compose.material.icons.filled.KeyboardArrowUp
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.toMediumWeight
+import com.android.settingslib.spa.framework.util.annotatedStringResource
+
+/** The widget model for [TopIntroPreference] widget. */
+interface TopIntroPreferenceModel {
+    /** The content of this [TopIntroPreference]. */
+    val text: String
+
+    /** The text clicked to expand this [TopIntroPreference]. */
+    val expandText: String
+
+    /** The text clicked to collapse this [TopIntroPreference]. */
+    val collapseText: String
+
+    /** The text clicked to open other resources. Should be a resource Id. */
+    val labelText: Int?
+}
+
+@Composable
+fun TopIntroPreference(model: TopIntroPreferenceModel) {
+    var expanded by remember { mutableStateOf(false) }
+    Column(Modifier.background(MaterialTheme.colorScheme.surfaceContainer)) {
+        // TopIntroPreference content.
+        Column(
+            modifier =
+                Modifier.padding(
+                        horizontal = SettingsDimension.paddingExtraLarge,
+                        vertical = SettingsDimension.paddingSmall,
+                    )
+                    .animateContentSize()
+        ) {
+            Text(
+                text = model.text,
+                style = MaterialTheme.typography.bodyLarge,
+                maxLines = if (expanded) MAX_LINE else MIN_LINE,
+            )
+            if (expanded) TopIntroAnnotatedText(model.labelText)
+        }
+
+        // TopIntroPreference collapse bar.
+        Row(
+            verticalAlignment = Alignment.CenterVertically,
+            modifier =
+                Modifier.fillMaxWidth()
+                    .clickable(onClick = { expanded = !expanded })
+                    .padding(
+                        top = SettingsDimension.paddingSmall,
+                        bottom = SettingsDimension.paddingLarge,
+                        start = SettingsDimension.paddingExtraLarge,
+                        end = SettingsDimension.paddingExtraLarge,
+                    ),
+        ) {
+            Icon(
+                imageVector =
+                    if (expanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown,
+                contentDescription = null,
+                modifier =
+                    Modifier.size(SettingsDimension.itemIconSize)
+                        .clip(CircleShape)
+                        .background(MaterialTheme.colorScheme.surfaceContainerHighest),
+            )
+            Text(
+                text = if (expanded) model.collapseText else model.expandText,
+                modifier = Modifier.padding(start = SettingsDimension.paddingSmall),
+                style = MaterialTheme.typography.bodyLarge.toMediumWeight(),
+                color = MaterialTheme.colorScheme.onSurface,
+            )
+        }
+    }
+}
+
+@Composable
+private fun TopIntroAnnotatedText(@StringRes id: Int?) {
+    if (id != null) {
+        Box(
+            Modifier.padding(
+                top = SettingsDimension.paddingExtraSmall5,
+                bottom = SettingsDimension.paddingExtraSmall5,
+                end = SettingsDimension.paddingLarge,
+            )
+        ) {
+            Text(
+                text = annotatedStringResource(id),
+                style = MaterialTheme.typography.bodyLarge.toMediumWeight(),
+                color = MaterialTheme.colorScheme.primary,
+            )
+        }
+    }
+}
+
+@Preview
+@Composable
+private fun TopIntroPreferencePreview() {
+    TopIntroPreference(
+        object : TopIntroPreferenceModel {
+            override val text =
+                "Additional text needed for the page. This can sit on the right side of the screen in 2 column.\n" +
+                    "Example collapsed text area that you will not see until you expand this block."
+            override val expandText = "Expand"
+            override val collapseText = "Collapse"
+            override val labelText = androidx.appcompat.R.string.abc_prepend_shortcut_label
+        }
+    )
+}
+
+const val MIN_LINE = 2
+const val MAX_LINE = 10
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
new file mode 100644
index 0000000..3f2e772
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.History
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.asComposePath
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.graphics.shapes.CornerRounding
+import androidx.graphics.shapes.RoundedPolygon
+import androidx.graphics.shapes.star
+import androidx.graphics.shapes.toPath
+
+@Composable
+fun ZeroStatePreference(icon: ImageVector, text: String? = null, description: String? = null) {
+    val zeroStateShape = remember {
+        RoundedPolygon.star(
+            numVerticesPerRadius = 6,
+            innerRadius = 0.75f,
+            rounding = CornerRounding(0.3f)
+        )
+    }
+    val clip = remember(zeroStateShape) {
+        RoundedPolygonShape(polygon = zeroStateShape)
+    }
+    Column(horizontalAlignment = Alignment.CenterHorizontally) {
+        Box(
+            modifier = Modifier
+                .clip(clip)
+                .background(MaterialTheme.colorScheme.primary)
+                .size(160.dp)
+        ) {
+            Icon(
+                imageVector = icon,
+                modifier = Modifier
+                    .align(Alignment.Center)
+                    .size(72.dp),
+                tint = MaterialTheme.colorScheme.onPrimary,
+                contentDescription = null,
+            )
+        }
+        if (text != null) {
+            Text(
+                text = text,
+                textAlign = TextAlign.Center,
+                style = MaterialTheme.typography.titleMedium,
+                color = MaterialTheme.colorScheme.onSurfaceVariant,
+                modifier = Modifier.padding(top = 24.dp),
+            )
+        }
+        if (description != null) {
+            Box {
+                Text(
+                    text = description,
+                    textAlign = TextAlign.Center,
+                    style = MaterialTheme.typography.bodyMedium,
+                    color = MaterialTheme.colorScheme.onSurfaceVariant,
+                )
+            }
+        }
+    }
+}
+
+@Preview
+@Composable
+private fun ZeroStatePreferencePreview() {
+    ZeroStatePreference(
+        Icons.Filled.History,
+        "No recent search history",
+        "Description"
+    )
+}
+
+class RoundedPolygonShape(
+    private val polygon: RoundedPolygon,
+    private var matrix: Matrix = Matrix()
+) : Shape {
+    private var path = Path()
+    override fun createOutline(
+        size: Size,
+        layoutDirection: LayoutDirection,
+        density: Density
+    ): Outline {
+        path.rewind()
+        path = polygon.toPath().asComposePath()
+
+        matrix.reset()
+        matrix.scale(size.width / 2f, size.height / 2f)
+        matrix.translate(1f, 1f)
+        matrix.rotateZ(30.0f)
+
+        path.transform(matrix)
+        return Outline.Generic(path)
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/res/values/strings.xml b/packages/SettingsLib/Spa/tests/res/values/strings.xml
index fb8f878..346f69b 100644
--- a/packages/SettingsLib/Spa/tests/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/tests/res/values/strings.xml
@@ -28,5 +28,7 @@
 
     <string name="test_annotated_string_resource">Annotated string with <b>bold</b> and <a href="https://www.android.com/">link</a>.</string>
 
+    <string name="test_top_intro_preference_label"><a href="https://www.android.com/">Label</a></string>
+
     <string name="test_link"><a href="https://www.android.com/">link</a></string>
 </resources>
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SystemPropertiesTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SystemPropertiesTest.kt
new file mode 100644
index 0000000..0827fa9
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SystemPropertiesTest.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.spa.framework.util
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SystemPropertiesTest {
+
+    @Test
+    fun getBoolean_noCrash() {
+        SystemProperties.getBoolean("is_expressive_design_enabled", false)
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt
new file mode 100644
index 0000000..5d80145
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class IntroPreferenceTest {
+    @get:Rule val composeTestRule = createComposeRule()
+
+    @Test
+    fun title_displayed() {
+        composeTestRule.setContent { IntroPreference(title = TITLE) }
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun description_displayed() {
+        composeTestRule.setContent { IntroPreference(title = TITLE, descriptions = DESCRIPTION) }
+
+        composeTestRule.onNodeWithText(DESCRIPTION.component1()).assertIsDisplayed()
+        composeTestRule.onNodeWithText(DESCRIPTION.component2()).assertIsNotDisplayed()
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+        val DESCRIPTION = listOf("Description", "")
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TopIntroPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TopIntroPreferenceTest.kt
new file mode 100644
index 0000000..62a71d4
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TopIntroPreferenceTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.spa.widget.preference
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.test.R
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TopIntroPreferenceTest {
+    @get:Rule val composeTestRule = createComposeRule()
+
+    @Test
+    fun content_collapsed_displayed() {
+        composeTestRule.setContent {
+            TopIntroPreference(
+                object : TopIntroPreferenceModel {
+                    override val text = TEXT
+                    override val expandText = EXPAND_TEXT
+                    override val collapseText = COLLAPSE_TEXT
+                    override val labelText = R.string.test_top_intro_preference_label
+                }
+            )
+        }
+
+        composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+        composeTestRule.onNodeWithText(EXPAND_TEXT).assertIsDisplayed()
+    }
+
+    @Test
+    fun content_expended_displayed() {
+        composeTestRule.setContent {
+            TopIntroPreference(
+                object : TopIntroPreferenceModel {
+                    override val text = TEXT
+                    override val expandText = EXPAND_TEXT
+                    override val collapseText = COLLAPSE_TEXT
+                    override val labelText = R.string.test_top_intro_preference_label
+                }
+            )
+        }
+
+        composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+        composeTestRule.onNodeWithText(EXPAND_TEXT).assertIsDisplayed().performClick()
+        composeTestRule.onNodeWithText(COLLAPSE_TEXT).assertIsDisplayed()
+        composeTestRule.onNodeWithText(LABEL_TEXT).assertIsDisplayed()
+    }
+
+    private companion object {
+        const val TEXT = "Text"
+        const val EXPAND_TEXT = "Expand"
+        const val COLLAPSE_TEXT = "Collapse"
+        const val LABEL_TEXT = "Label"
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ZeroStatePreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ZeroStatePreferenceTest.kt
new file mode 100644
index 0000000..99ac27c
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ZeroStatePreferenceTest.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.spa.widget.preference
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.History
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ZeroStatePreferenceTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun title_displayed() {
+        composeTestRule.setContent {
+            ZeroStatePreference(Icons.Filled.History, TITLE)
+        }
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun description_displayed() {
+        composeTestRule.setContent {
+            ZeroStatePreference(Icons.Filled.History, TITLE, DESCRIPTION)
+        }
+
+        composeTestRule.onNodeWithText(DESCRIPTION).assertIsDisplayed()
+    }
+
+    private companion object {
+        const val TITLE = "Title"
+        const val DESCRIPTION = "Description"
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml b/packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml
new file mode 100644
index 0000000..4347ef2
--- /dev/null
+++ b/packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:gravity="center_vertical"
+    android:background="?android:attr/selectableItemBackground"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:clipToPadding="false">
+
+    <include layout="@layout/settingslib_expressive_preference_icon_frame"/>
+
+    <include layout="@layout/settingslib_expressive_preference_text_frame" />
+
+    <include layout="@layout/settingslib_expressive_two_target_divider" />
+
+    <!-- Preference should place its actual preference widget here. -->
+    <LinearLayout
+        android:id="@android:id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:minWidth="@dimen/two_target_min_width"
+        android:gravity="center"
+        android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
index b125f71..58ff0ce 100644
--- a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
+++ b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
@@ -72,7 +72,10 @@
     }
 
     private void init(Context context) {
-        setLayoutResource(R.layout.preference_two_target);
+        int resID = SettingsThemeHelper.isExpressiveTheme(context)
+                ? R.layout.settingslib_expressive_preference_two_target
+                : R.layout.preference_two_target;
+        setLayoutResource(resID);
         mSmallIconSize = context.getResources().getDimensionPixelSize(
                 R.dimen.two_target_pref_small_icon_size);
         mMediumIconSize = context.getResources().getDimensionPixelSize(
diff --git a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
index e41126f..2475c8e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
@@ -31,6 +31,8 @@
 
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 import com.android.settingslib.core.instrumentation.SettingsJankMonitor;
+import com.android.settingslib.widget.SettingsThemeHelper;
+import com.android.settingslib.widget.theme.R;
 
 /**
  * A custom preference that provides inline switch toggle. It has a mandatory field for title, and
@@ -62,7 +64,9 @@
 
     @Override
     protected int getSecondTargetResId() {
-        return androidx.preference.R.layout.preference_widget_switch_compat;
+        return SettingsThemeHelper.isExpressiveTheme(getContext())
+                ? R.layout.settingslib_expressive_preference_switch
+                : androidx.preference.R.layout.preference_widget_switch_compat;
     }
 
     @Override
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index f64c305..749ad0a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -967,7 +967,7 @@
         for (int i = 0; i < nameCount; i++) {
             String name = names.get(i);
             Setting setting = settingsState.getSettingLocked(name);
-            pw.print("_id:"); pw.print(toDumpString(setting.getId()));
+            pw.print("_id:"); pw.print(toDumpString(String.valueOf(setting.getId())));
             pw.print(" name:"); pw.print(toDumpString(name));
             if (setting.getPackageName() != null) {
                 pw.print(" pkg:"); pw.print(setting.getPackageName());
@@ -2785,7 +2785,7 @@
 
             switch (column) {
                 case Settings.NameValueTable._ID -> {
-                    values[i] = setting.getId();
+                    values[i] = String.valueOf(setting.getId());
                 }
                 case Settings.NameValueTable.NAME -> {
                     values[i] = setting.getName();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 452edd9..3c634f0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -517,6 +517,7 @@
         }
 
         String namespace = name.substring(0, slashIdx);
+        namespace = namespace.intern();  // Many configs have the same namespace.
         String fullFlagName = name.substring(slashIdx + 1);
         boolean isLocal = false;
 
@@ -566,7 +567,7 @@
         }
         try {
             localCounter = Integer.parseInt(markerSetting.value);
-        } catch(NumberFormatException e) {
+        } catch (NumberFormatException e) {
             // reset local counter
             markerSetting.value = "0";
         }
@@ -1363,7 +1364,10 @@
                     }
 
                     try {
-                        if (writeSingleSetting(mVersion, serializer, setting.getId(),
+                        if (writeSingleSetting(
+                                mVersion,
+                                serializer,
+                                Long.toString(setting.getId()),
                                 setting.getName(),
                                 setting.getValue(), setting.getDefaultValue(),
                                 setting.getPackageName(),
@@ -1632,7 +1636,7 @@
             TypedXmlPullParser parser = Xml.resolvePullParser(in);
             parseStateLocked(parser);
             return true;
-        } catch (XmlPullParserException | IOException e) {
+        } catch (XmlPullParserException | IOException | NumberFormatException e) {
             Slog.e(LOG_TAG, "parse settings xml failed", e);
             return false;
         } finally {
@@ -1652,7 +1656,7 @@
     }
 
     private void parseStateLocked(TypedXmlPullParser parser)
-            throws IOException, XmlPullParserException {
+            throws IOException, XmlPullParserException, NumberFormatException {
         final int outerDepth = parser.getDepth();
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1708,7 +1712,7 @@
 
     @GuardedBy("mLock")
     private void parseSettingsLocked(TypedXmlPullParser parser)
-            throws IOException, XmlPullParserException {
+            throws IOException, XmlPullParserException, NumberFormatException {
 
         mVersion = parser.getAttributeInt(null, ATTR_VERSION);
 
@@ -1776,7 +1780,7 @@
                     }
                 }
                 mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag,
-                        fromSystem, id, isPreservedInRestore));
+                        fromSystem, Long.valueOf(id), isPreservedInRestore));
 
                 if (DEBUG_PERSISTENCE) {
                     Slog.i(LOG_TAG, "[RESTORED] " + name + "=" + value);
@@ -1866,7 +1870,7 @@
         private String value;
         private String defaultValue;
         private String packageName;
-        private String id;
+        private long id;
         private String tag;
         // Whether the default is set by the system
         private boolean defaultFromSystem;
@@ -1898,30 +1902,27 @@
         }
 
         public Setting(String name, String value, String defaultValue,
-                String packageName, String tag, boolean fromSystem, String id) {
+                String packageName, String tag, boolean fromSystem, long id) {
             this(name, value, defaultValue, packageName, tag, fromSystem, id,
                     /* isOverrideableByRestore */ false);
         }
 
         Setting(String name, String value, String defaultValue,
-                String packageName, String tag, boolean fromSystem, String id,
+                String packageName, String tag, boolean fromSystem, long id,
                 boolean isValuePreservedInRestore) {
-            mNextId = Math.max(mNextId, Long.parseLong(id) + 1);
-            if (NULL_VALUE.equals(value)) {
-                value = null;
-            }
+            mNextId = Math.max(mNextId, id + 1);
             init(name, value, tag, defaultValue, packageName, fromSystem, id,
                     isValuePreservedInRestore);
         }
 
         private void init(String name, String value, String tag, String defaultValue,
-                String packageName, boolean fromSystem, String id,
+                String packageName, boolean fromSystem, long id,
                 boolean isValuePreservedInRestore) {
             this.name = name;
-            this.value = value;
+            this.value = internValue(value);
             this.tag = tag;
-            this.defaultValue = defaultValue;
-            this.packageName = packageName;
+            this.defaultValue = internValue(defaultValue);
+            this.packageName = TextUtils.safeIntern(packageName);
             this.id = id;
             this.defaultFromSystem = fromSystem;
             this.isValuePreservedInRestore = isValuePreservedInRestore;
@@ -1959,7 +1960,7 @@
             return isValuePreservedInRestore;
         }
 
-        public String getId() {
+        public long getId() {
             return id;
         }
 
@@ -1992,9 +1993,6 @@
         private boolean update(String value, boolean setDefault, String packageName, String tag,
                 boolean forceNonSystemPackage, boolean overrideableByRestore,
                 boolean resetToDefault) {
-            if (NULL_VALUE.equals(value)) {
-                value = null;
-            }
             final boolean callerSystem = !forceNonSystemPackage &&
                     !isNull() && (isCalledFromSystem(packageName)
                     || isSystemPackage(mContext, packageName));
@@ -2039,7 +2037,7 @@
             }
 
             init(name, value, tag, defaultValue, packageName, defaultFromSystem,
-                    String.valueOf(mNextId++), isPreserved);
+                    mNextId++, isPreserved);
 
             return true;
         }
@@ -2051,6 +2049,32 @@
                     + " defaultFromSystem=" + defaultFromSystem + "}";
         }
 
+        /**
+         * Interns a string if it's a common setting value.
+         * Otherwise returns the given string.
+         */
+        static String internValue(String str) {
+            if (str == null) {
+                return null;
+            }
+            switch (str) {
+                case "true":
+                    return "true";
+                case "false":
+                    return "false";
+                case "0":
+                    return "0";
+                case "1":
+                    return "1";
+                case "":
+                    return "";
+                case "null":
+                    return null;  // explicit null has special handling
+                default:
+                    return str;
+            }
+        }
+
         private boolean shouldPreserveSetting(boolean overrideableByRestore,
                 boolean resetToDefault, String packageName, String value) {
             if (resetToDefault) {
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index c49ffb4..f8383d9 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -149,16 +149,6 @@
 }
 
 flag {
-   name: "modes_dialog_single_rows"
-   namespace: "systemui"
-   description: "[Experiment] Display one entry per grid row in the Modes Dialog."
-   bug: "366034002"
-   metadata {
-        purpose: PURPOSE_BUGFIX
-   }
-}
-
-flag {
    name: "pss_app_selector_recents_split_screen"
    namespace: "systemui"
    description: "Allows recent apps selected for partial screenshare to be launched in split screen mode"
@@ -599,6 +589,16 @@
 }
 
 flag {
+    name: "clipboard_use_description_mimetype"
+    namespace: "systemui"
+    description: "Read item mimetype from description rather than checking URI"
+    bug: "357197236"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "screenshot_action_dismiss_system_windows"
     namespace: "systemui"
     description: "Dismiss existing system windows when starting action from screenshot UI"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
index 907c39d..f5d01d7 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
@@ -944,26 +944,9 @@
                 }
 
                 override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
-                    // onLaunchAnimationEnd is called by an Animator at the end of the animation,
-                    // on a Choreographer animation tick. The following calls will move the animated
-                    // content from the dialog overlay back to its original position, and this
-                    // change must be reflected in the next frame given that we then sync the next
-                    // frame of both the content and dialog ViewRoots. However, in case that content
-                    // is rendered by Compose, whose compositions are also scheduled on a
-                    // Choreographer frame, any state change made *right now* won't be reflected in
-                    // the next frame given that a Choreographer frame can't schedule another and
-                    // have it happen in the same frame. So we post the forwarded calls to
-                    // [Controller.onLaunchAnimationEnd], leaving this Choreographer frame, ensuring
-                    // that the move of the content back to its original window will be reflected in
-                    // the next frame right after [onLaunchAnimationEnd] is called.
-                    //
-                    // TODO(b/330672236): Move this to TransitionAnimator.
-                    dialog.context.mainExecutor.execute {
-                        startController.onTransitionAnimationEnd(isExpandingFullyAbove)
-                        endController.onTransitionAnimationEnd(isExpandingFullyAbove)
-
-                        onLaunchAnimationEnd()
-                    }
+                    startController.onTransitionAnimationEnd(isExpandingFullyAbove)
+                    endController.onTransitionAnimationEnd(isExpandingFullyAbove)
+                    onLaunchAnimationEnd()
                 }
 
                 override fun onTransitionAnimationProgress(
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index fc4cf1d..859fc4e0 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -379,13 +379,26 @@
                         Log.d(TAG, "Animation ended")
                     }
 
-                    // TODO(b/330672236): Post this to the main thread instead so that it does not
-                    // flicker with Flexiglass enabled.
-                    controller.onTransitionAnimationEnd(isExpandingFullyAbove)
-                    transitionContainerOverlay.remove(windowBackgroundLayer)
+                    // onAnimationEnd is called at the end of the animation, on a Choreographer
+                    // animation tick. During dialog launches, the following calls will move the
+                    // animated content from the dialog overlay back to its original position, and
+                    // this change must be reflected in the next frame given that we then sync the
+                    // next frame of both the content and dialog ViewRoots. During SysUI activity
+                    // launches, we will instantly collapse the shade at the end of the transition.
+                    // However, if those are rendered by Compose, whose compositions are also
+                    // scheduled on a Choreographer frame, any state change made *right now* won't
+                    // be reflected in the next frame given that a Choreographer frame can't
+                    // schedule another and have it happen in the same frame. So we post the
+                    // forwarded calls to [Controller.onLaunchAnimationEnd] in the main executor,
+                    // leaving this Choreographer frame, ensuring that any state change applied by
+                    // onTransitionAnimationEnd() will be reflected in the same frame.
+                    mainExecutor.execute {
+                        controller.onTransitionAnimationEnd(isExpandingFullyAbove)
+                        transitionContainerOverlay.remove(windowBackgroundLayer)
 
-                    if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
-                        openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
+                        if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
+                            openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
+                        }
                     }
                 }
             }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 1b99a96..fe4a65b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -130,8 +130,8 @@
 fun SceneScope.HeadsUpNotificationSpace(
     stackScrollView: NotificationScrollView,
     viewModel: NotificationsPlaceholderViewModel,
+    useHunBounds: () -> Boolean = { true },
     modifier: Modifier = Modifier,
-    isPeekFromBottom: Boolean = false,
 ) {
     Box(
         modifier =
@@ -141,17 +141,25 @@
                 .notificationHeadsUpHeight(stackScrollView)
                 .debugBackground(viewModel, DEBUG_HUN_COLOR)
                 .onGloballyPositioned { coordinates: LayoutCoordinates ->
-                    val positionInWindow = coordinates.positionInWindow()
-                    val boundsInWindow = coordinates.boundsInWindow()
-                    debugLog(viewModel) {
-                        "HUNS onGloballyPositioned:" +
-                            " size=${coordinates.size}" +
-                            " bounds=$boundsInWindow"
+                    // This element is sometimes opted out of the shared element system, so there
+                    // can be multiple instances of it during a transition. Thus we need to
+                    // determine which instance should feed its bounds to NSSL to avoid providing
+                    // conflicting values
+                    val useBounds = useHunBounds()
+                    if (useBounds) {
+                        val positionInWindow = coordinates.positionInWindow()
+                        val boundsInWindow = coordinates.boundsInWindow()
+                        debugLog(viewModel) {
+                            "HUNS onGloballyPositioned:" +
+                                " size=${coordinates.size}" +
+                                " bounds=$boundsInWindow"
+                        }
+                        // Note: boundsInWindow doesn't scroll off the screen, so use
+                        // positionInWindow
+                        // for top bound, which can scroll off screen while snoozing
+                        stackScrollView.setHeadsUpTop(positionInWindow.y)
+                        stackScrollView.setHeadsUpBottom(boundsInWindow.bottom)
                     }
-                    // Note: boundsInWindow doesn't scroll off the screen, so use positionInWindow
-                    // for top bound, which can scroll off screen while snoozing
-                    stackScrollView.setHeadsUpTop(positionInWindow.y)
-                    stackScrollView.setHeadsUpBottom(boundsInWindow.bottom)
                 }
     )
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index d91958a..0c69dbd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -416,11 +416,11 @@
         HeadsUpNotificationSpace(
             stackScrollView = notificationStackScrollView,
             viewModel = notificationsPlaceholderViewModel,
+            useHunBounds = { shouldUseQuickSettingsHunBounds(layoutState.transitionState) },
             modifier =
                 Modifier.align(Alignment.BottomCenter)
                     .navigationBarsPadding()
                     .padding(horizontal = shadeHorizontalPadding),
-            isPeekFromBottom = true,
         )
         NotificationScrollingStack(
             shadeSession = shadeSession,
@@ -446,3 +446,7 @@
         )
     }
 }
+
+private fun shouldUseQuickSettingsHunBounds(state: TransitionState): Boolean {
+    return state is TransitionState.Idle && state.currentScene == Scenes.QuickSettings
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index f64d0ed..58fbf43 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -77,6 +77,10 @@
     }
     from(Scenes.Lockscreen, to = Scenes.QuickSettings) { lockscreenToQuickSettingsTransition() }
     from(Scenes.Lockscreen, to = Scenes.Gone) { lockscreenToGoneTransition() }
+    from(Scenes.QuickSettings, to = Scenes.Shade) {
+        reversed { shadeToQuickSettingsTransition() }
+        sharedElement(Notifications.Elements.HeadsUpNotificationPlaceholder, enabled = false)
+    }
     from(Scenes.Shade, to = Scenes.QuickSettings) { shadeToQuickSettingsTransition() }
 
     // Overlay transitions
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSliceViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSliceViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/NumPadAnimatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/NumPadAnimatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/PinShapeHintingViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/PinShapeHintingViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TestScopeProvider.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/TestScopeProvider.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/TestScopeProvider.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/TestScopeProvider.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 492543f..af3ddfc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -310,6 +310,41 @@
                 .isEqualTo(displayId)
         }
 
+    @Test
+    fun afterSuccessfulAuthentication_focusIsNotRequested() =
+        testScope.runTest {
+            val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
+            val textInputFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested)
+            lockDeviceAndOpenPasswordBouncer()
+
+            // remove focus from text field
+            underTest.onTextFieldFocusChanged(false)
+            runCurrent()
+
+            // focus should be requested
+            assertThat(textInputFocusRequested).isTrue()
+
+            // simulate text field getting focus
+            underTest.onTextFieldFocusChanged(true)
+            runCurrent()
+
+            // focus should not be requested anymore
+            assertThat(textInputFocusRequested).isFalse()
+
+            // authenticate successfully.
+            underTest.onPasswordInputChanged("password")
+            underTest.onAuthenticateKeyPressed()
+            runCurrent()
+
+            assertThat(authResult).isTrue()
+
+            // remove focus from text field
+            underTest.onTextFieldFocusChanged(false)
+            runCurrent()
+            // focus should not be requested again
+            assertThat(textInputFocusRequested).isFalse()
+        }
+
     private fun TestScope.switchToScene(toScene: SceneKey) {
         val currentScene by collectLastValue(sceneInteractor.currentScene)
         val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer
@@ -327,10 +362,7 @@
         switchToScene(Scenes.Bouncer)
     }
 
-    private suspend fun TestScope.setLockout(
-        isLockedOut: Boolean,
-        failedAttemptCount: Int = 5,
-    ) {
+    private suspend fun TestScope.setLockout(isLockedOut: Boolean, failedAttemptCount: Int = 5) {
         if (isLockedOut) {
             repeat(failedAttemptCount) {
                 kosmos.fakeAuthenticationRepository.reportAuthenticationAttempt(false)
@@ -350,7 +382,7 @@
         kosmos.fakeUserRepository.selectedUser.value =
             SelectedUserModel(
                 userInfo = userInfo,
-                selectionStatus = SelectionStatus.SELECTION_COMPLETE
+                selectionStatus = SelectionStatus.SELECTION_COMPLETE,
             )
         advanceTimeBy(PasswordBouncerViewModel.DELAY_TO_FETCH_IMES)
     }
@@ -374,7 +406,7 @@
             subtypes =
                 List(auxiliarySubtypes + nonAuxiliarySubtypes) {
                     InputMethodModel.Subtype(subtypeId = it, isAuxiliary = it < auxiliarySubtypes)
-                }
+                },
         )
     }
 
@@ -383,9 +415,6 @@
         private const val WRONG_PASSWORD = "Wrong password"
 
         private val USER_INFOS =
-            listOf(
-                UserInfo(100, "First user", 0),
-                UserInfo(101, "Second user", 0),
-            )
+            listOf(UserInfo(100, "First user", 0), UserInfo(101, "Second user", 0))
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
index 2ba4bf9..e25c1a7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
@@ -34,8 +34,6 @@
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.realKeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.DozeStateModel
-import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
@@ -50,8 +48,6 @@
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.seconds
@@ -220,15 +216,11 @@
     @Test
     fun transition_from_hub_end_in_dream() =
         testScope.runTest {
-            // Device is dreaming and not dozing.
-            kosmos.powerInteractor.setAwakeForTest()
-            kosmos.fakeKeyguardRepository.setDozeTransitionModel(
-                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
-            )
+            // Device is dreaming and occluded.
             kosmos.fakeKeyguardRepository.setKeyguardOccluded(true)
             kosmos.fakeKeyguardRepository.setDreaming(true)
             kosmos.fakeKeyguardRepository.setDreamingWithOverlay(true)
-            advanceTimeBy(600L)
+            runCurrent()
 
             sceneTransitions.value = hubToBlank
 
@@ -663,7 +655,7 @@
                     from = LOCKSCREEN,
                     to = OCCLUDED,
                     animator = null,
-                    modeOnCanceled = TransitionModeOnCanceled.RESET
+                    modeOnCanceled = TransitionModeOnCanceled.RESET,
                 )
             )
 
@@ -750,7 +742,7 @@
                     from = LOCKSCREEN,
                     to = OCCLUDED,
                     animator = null,
-                    modeOnCanceled = TransitionModeOnCanceled.RESET
+                    modeOnCanceled = TransitionModeOnCanceled.RESET,
                 )
             )
 
@@ -852,8 +844,8 @@
                     to = ALTERNATE_BOUNCER,
                     animator = null,
                     ownerName = "external",
-                    modeOnCanceled = TransitionModeOnCanceled.RESET
-                ),
+                    modeOnCanceled = TransitionModeOnCanceled.RESET,
+                )
             )
 
             val allSteps by collectValues(keyguardTransitionRepository.transitions)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt
index b3ffc71..d6734e8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt
@@ -19,6 +19,7 @@
 import android.app.ActivityManager.RunningTaskInfo
 import android.app.usage.UsageEvents
 import android.content.pm.UserInfo
+import android.service.dream.dreamManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -28,6 +29,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.settings.fakeUserTracker
 import com.android.systemui.shared.system.taskStackChangeListeners
@@ -48,6 +50,7 @@
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
 import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
 import org.mockito.kotlin.times
@@ -90,6 +93,27 @@
         }
 
     @Test
+    fun testNewTaskStartsWhileOnHub_stopsDream() =
+        testScope.runTest {
+            transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
+            backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() }
+            runCurrent()
+
+            verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any())
+            moveTaskToFront()
+
+            argumentCaptor<OnDismissAction>().apply {
+                verify(activityStarter).dismissKeyguardThenExecute(capture(), anyOrNull(), any())
+
+                firstValue.onDismiss()
+                runCurrent()
+
+                // Dream is stopped once keyguard is dismissed.
+                verify(kosmos.dreamManager).stopDream()
+            }
+        }
+
+    @Test
     fun testNewTaskStartsAfterExitingHub_doesNotTriggerUnlock() =
         testScope.runTest {
             transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
@@ -209,7 +233,7 @@
                     ownerName = "test",
                 ),
             ),
-            testScope
+            testScope,
         )
         runCurrent()
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt
index 9da6885..52ed231 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt
@@ -19,11 +19,13 @@
 import android.content.Context
 import android.content.Intent
 import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.broadcastDispatcher
 import com.android.systemui.inputdevice.tutorial.ui.TutorialNotificationCoordinator
+import com.android.systemui.shared.Flags
 import com.android.systemui.testKosmos
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -43,16 +45,17 @@
         KeyboardTouchpadTutorialCoreStartable(
             { mock<TutorialNotificationCoordinator>() },
             broadcastDispatcher,
-            context
+            context,
         )
 
     @Test
+    @EnableFlags(Flags.FLAG_NEW_TOUCHPAD_GESTURES_TUTORIAL)
     fun registersBroadcastReceiverStartingActivityAsSystemUser() {
         underTest.start()
 
         broadcastDispatcher.sendIntentToMatchingReceiversOnly(
             context,
-            Intent("com.android.systemui.action.KEYBOARD_TOUCHPAD_TUTORIAL")
+            Intent("com.android.systemui.action.KEYBOARD_TOUCHPAD_TUTORIAL"),
         )
 
         verify(context).startActivityAsUser(any(), eq(UserHandle.SYSTEM))
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 bd6cfff..93754fd 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
@@ -31,6 +31,7 @@
 import com.android.systemui.doze.DozeTransitionCallback
 import com.android.systemui.doze.DozeTransitionListener
 import com.android.systemui.dreams.DreamOverlayCallbackController
+import com.android.systemui.flags.DisableSceneContainer
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.DozeStateModel
@@ -288,6 +289,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun dozeAmount() =
         testScope.runTest {
             val values = mutableListOf<Float>()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index fac9312..ff0a4a1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -26,8 +26,10 @@
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
 import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
+import com.android.systemui.communal.data.repository.communalSceneRepository
 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
 import com.android.systemui.communal.shared.model.CommunalScenes
@@ -50,10 +52,12 @@
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
+import com.google.common.truth.Truth
 import junit.framework.Assert.assertEquals
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -219,6 +223,28 @@
         }
 
     @Test
+    @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR, FLAG_SCENE_CONTAINER)
+    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+    fun testTransitionToGlanceableHub_onWakeup_ifAvailable() =
+        testScope.runTest {
+            // Hub is available.
+            whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+            kosmos.setCommunalAvailable(true)
+            runCurrent()
+
+            // Device turns on.
+            powerInteractor.setAwakeForTest()
+            advanceTimeBy(50L)
+            runCurrent()
+
+            // We transition to the hub when waking up.
+            Truth.assertThat(kosmos.communalSceneRepository.currentScene.value)
+                .isEqualTo(CommunalScenes.Communal)
+            // No transitions are directly started by this interactor.
+            assertThat(transitionRepository).noTransitionsStarted()
+        }
+
+    @Test
     @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
     fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop() =
         testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index 638c957..a08fbbf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -16,12 +16,19 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
+import android.service.dream.dreamManager
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -36,6 +43,7 @@
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -43,26 +51,52 @@
 import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.spy
+import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class FromDreamingTransitionInteractorTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class FromDreamingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiTestCase() {
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+                .andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags!!)
+    }
+
     private val kosmos =
         testKosmos().apply {
             this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
         }
 
     private val testScope = kosmos.testScope
-    private val underTest = kosmos.fromDreamingTransitionInteractor
+    private val underTest by lazy { kosmos.fromDreamingTransitionInteractor }
 
     private val powerInteractor = kosmos.powerInteractor
     private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
 
     @Before
     fun setup() {
+        runBlocking {
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.DREAMING,
+                testScope,
+            )
+            reset(transitionRepository)
+            kosmos.setCommunalAvailable(true)
+        }
         underTest.start()
     }
 
@@ -86,10 +120,7 @@
             runCurrent()
 
             assertThat(transitionRepository)
-                .startedTransition(
-                    from = KeyguardState.DREAMING,
-                    to = KeyguardState.OCCLUDED,
-                )
+                .startedTransition(from = KeyguardState.DREAMING, to = KeyguardState.OCCLUDED)
         }
 
     @Test
@@ -126,7 +157,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.DREAMING,
-                testScope
+                testScope,
             )
             kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE)
 
@@ -139,10 +170,7 @@
             advanceTimeBy(60L)
 
             assertThat(transitionRepository)
-                .startedTransition(
-                    from = KeyguardState.DREAMING,
-                    to = KeyguardState.LOCKSCREEN,
-                )
+                .startedTransition(from = KeyguardState.DREAMING, to = KeyguardState.LOCKSCREEN)
         }
 
     @Test
@@ -164,4 +192,25 @@
                     to = KeyguardState.ALTERNATE_BOUNCER,
                 )
         }
+
+    @Test
+    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+    @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
+    fun testTransitionToGlanceableHubOnWake() =
+        testScope.runTest {
+            whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+            kosmos.setCommunalAvailable(true)
+            runCurrent()
+
+            // Device wakes up.
+            powerInteractor.setAwakeForTest()
+            advanceTimeBy(150L)
+            runCurrent()
+
+            // We transition to the hub when waking up.
+            assertThat(kosmos.communalSceneRepository.currentScene.value)
+                .isEqualTo(CommunalScenes.Communal)
+            // No transitions are directly started by this interactor.
+            assertThat(transitionRepository).noTransitionsStarted()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index 41cc953..ba68917 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -81,6 +81,7 @@
                 powerInteractor = kosmos.powerInteractor,
                 alternateBouncerInteractor = kosmos.alternateBouncerInteractor,
                 shadeInteractor = { kosmos.shadeInteractor },
+                keyguardInteractor = { kosmos.keyguardInteractor },
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index b843fd5..3fb3eea 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -29,15 +29,21 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.keyguard.data.repository.fakeCommandQueue
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.keyguard.shared.model.CameraLaunchType
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 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.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
@@ -67,12 +73,11 @@
     private val testScope = kosmos.testScope
     private val repository by lazy { kosmos.fakeKeyguardRepository }
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
-    private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor }
-    private val commandQueue by lazy { kosmos.fakeCommandQueue }
     private val configRepository by lazy { kosmos.fakeConfigurationRepository }
     private val bouncerRepository by lazy { kosmos.keyguardBouncerRepository }
     private val shadeRepository by lazy { kosmos.shadeRepository }
     private val powerInteractor by lazy { kosmos.powerInteractor }
+    private val keyguardRepository by lazy { kosmos.keyguardRepository }
     private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
 
     private val transitionState: MutableStateFlow<ObservableTransitionState> =
@@ -178,8 +183,8 @@
             assertThat(dismissAlpha).isEqualTo(1f)
 
             keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.AOD,
-                to = KeyguardState.LOCKSCREEN,
+                from = AOD,
+                to = LOCKSCREEN,
                 testScope,
             )
 
@@ -204,8 +209,8 @@
             assertThat(dismissAlpha.size).isEqualTo(1)
 
             keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.AOD,
-                to = KeyguardState.LOCKSCREEN,
+                from = AOD,
+                to = LOCKSCREEN,
                 testScope,
             )
 
@@ -266,13 +271,13 @@
             keyguardTransitionRepository.sendTransitionSteps(
                 listOf(
                     TransitionStep(
-                        from = KeyguardState.AOD,
+                        from = AOD,
                         to = KeyguardState.GONE,
                         value = 0f,
-                        transitionState = TransitionState.STARTED,
+                        transitionState = STARTED,
                     ),
                     TransitionStep(
-                        from = KeyguardState.AOD,
+                        from = AOD,
                         to = KeyguardState.GONE,
                         value = 0.1f,
                         transitionState = TransitionState.RUNNING,
@@ -302,7 +307,7 @@
             shadeRepository.setLegacyShadeExpansion(0f)
 
             keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.AOD,
+                from = AOD,
                 to = KeyguardState.GONE,
                 testScope,
             )
@@ -324,8 +329,8 @@
             shadeRepository.setLegacyShadeExpansion(0f)
 
             keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.AOD,
-                to = KeyguardState.LOCKSCREEN,
+                from = AOD,
+                to = LOCKSCREEN,
                 testScope,
             )
 
@@ -346,8 +351,8 @@
             shadeRepository.setLegacyShadeExpansion(1f)
 
             keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.AOD,
-                to = KeyguardState.LOCKSCREEN,
+                from = AOD,
+                to = LOCKSCREEN,
                 testScope,
             )
 
@@ -370,13 +375,13 @@
             keyguardTransitionRepository.sendTransitionSteps(
                 listOf(
                     TransitionStep(
-                        from = KeyguardState.AOD,
+                        from = AOD,
                         to = KeyguardState.GONE,
                         value = 0f,
-                        transitionState = TransitionState.STARTED,
+                        transitionState = STARTED,
                     ),
                     TransitionStep(
-                        from = KeyguardState.AOD,
+                        from = AOD,
                         to = KeyguardState.GONE,
                         value = 0.1f,
                         transitionState = TransitionState.RUNNING,
@@ -468,4 +473,63 @@
             runCurrent()
             assertThat(isAnimate).isFalse()
         }
+
+    @Test
+    @EnableSceneContainer
+    fun dozeAmount_updatedByAodTransitionWhenAodEnabled() =
+        testScope.runTest {
+            val dozeAmount by collectLastValue(underTest.dozeAmount)
+
+            keyguardRepository.setAodAvailable(true)
+
+            sendTransitionStep(TransitionStep(to = AOD, value = 0f, transitionState = STARTED))
+            assertThat(dozeAmount).isEqualTo(0f)
+
+            sendTransitionStep(TransitionStep(to = AOD, value = 0.5f, transitionState = RUNNING))
+            assertThat(dozeAmount).isEqualTo(0.5f)
+
+            sendTransitionStep(TransitionStep(to = AOD, value = 1f, transitionState = FINISHED))
+            assertThat(dozeAmount).isEqualTo(1f)
+
+            sendTransitionStep(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+            assertThat(dozeAmount).isEqualTo(1f)
+
+            sendTransitionStep(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+            assertThat(dozeAmount).isEqualTo(0.5f)
+
+            sendTransitionStep(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+            assertThat(dozeAmount).isEqualTo(0f)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun dozeAmount_updatedByDozeTransitionWhenAodDisabled() =
+        testScope.runTest {
+            val dozeAmount by collectLastValue(underTest.dozeAmount)
+
+            keyguardRepository.setAodAvailable(false)
+
+            sendTransitionStep(TransitionStep(to = DOZING, value = 0f, transitionState = STARTED))
+            assertThat(dozeAmount).isEqualTo(0f)
+
+            sendTransitionStep(TransitionStep(to = DOZING, value = 0.5f, transitionState = RUNNING))
+            assertThat(dozeAmount).isEqualTo(0.5f)
+
+            sendTransitionStep(TransitionStep(to = DOZING, value = 1f, transitionState = FINISHED))
+            assertThat(dozeAmount).isEqualTo(1f)
+
+            sendTransitionStep(TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED))
+            assertThat(dozeAmount).isEqualTo(1f)
+
+            sendTransitionStep(TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING))
+            assertThat(dozeAmount).isEqualTo(0.5f)
+
+            sendTransitionStep(TransitionStep(DOZING, LOCKSCREEN, 1f, FINISHED))
+            assertThat(dozeAmount).isEqualTo(0f)
+        }
+
+    private suspend fun sendTransitionStep(step: TransitionStep) {
+        keyguardTransitionRepository.sendTransitionStep(step)
+        testScope.runCurrent()
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractorTest.kt
new file mode 100644
index 0000000..2558d58
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractorTest.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.policy.IKeyguardDismissCallback
+import com.android.internal.policy.IKeyguardStateCallback
+import com.android.keyguard.trustManager
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.dismissCallbackRegistry
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.domain.interactor.keyguardStateCallbackInteractor
+import com.android.systemui.testKosmos
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.time.fakeSystemClock
+import kotlin.test.Test
+import kotlinx.coroutines.test.currentTime
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.kotlin.atLeast
+import org.mockito.kotlin.atLeastOnce
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardStateCallbackInteractorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private lateinit var underTest: KeyguardStateCallbackInteractor
+    private lateinit var callback: IKeyguardStateCallback
+    private lateinit var systemClock: FakeSystemClock
+
+    @Before
+    fun setUp() {
+        systemClock = kosmos.fakeSystemClock
+        systemClock.setCurrentTimeMillis(testScope.currentTime)
+
+        underTest = kosmos.keyguardStateCallbackInteractor
+        underTest.start()
+
+        callback = mock<IKeyguardStateCallback>()
+    }
+
+    @Test
+    fun test_addCallback_passesInitialValues() =
+        testScope.runTest {
+            underTest.addCallback(callback)
+
+            verify(callback).onShowingStateChanged(anyBoolean(), anyInt())
+            verify(callback).onInputRestrictedStateChanged(anyBoolean())
+            verify(callback).onTrustedChanged(anyBoolean())
+            verify(callback).onSimSecureStateChanged(anyBoolean())
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun test_lockscreenVisibility_notifyDismissSucceeded_ifNotVisible() =
+        testScope.runTest {
+            underTest.addCallback(callback)
+
+            val dismissCallback = mock<IKeyguardDismissCallback>()
+            kosmos.dismissCallbackRegistry.addCallback(dismissCallback)
+            runCurrent()
+
+            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope = testScope,
+            )
+
+            systemClock.advanceTime(1) // Required for DismissCallbackRegistry's bgExecutor
+            verify(dismissCallback).onDismissSucceeded()
+
+            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.LOCKSCREEN,
+                testScope = testScope,
+            )
+
+            Mockito.verifyNoMoreInteractions(dismissCallback)
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun test_lockscreenVisibility_reportsKeyguardShowingChanged() =
+        testScope.runTest {
+            underTest.addCallback(callback)
+
+            Mockito.clearInvocations(callback)
+            Mockito.clearInvocations(kosmos.trustManager)
+
+            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope = testScope,
+            )
+            runCurrent()
+
+            verify(callback, atLeastOnce()).onShowingStateChanged(eq(false), anyInt())
+            verify(kosmos.trustManager, atLeastOnce()).reportKeyguardShowingChanged()
+
+            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.LOCKSCREEN,
+                testScope = testScope,
+            )
+
+            verify(callback, atLeastOnce()).onShowingStateChanged(eq(true), anyInt())
+            verify(kosmos.trustManager, atLeast(2)).reportKeyguardShowingChanged()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 77106ae..a617484 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1465,10 +1465,8 @@
 
             // WHEN the keyguard is occluded and device wakes up and is no longer dreaming
             keyguardRepository.setDreaming(false)
-            testScheduler.advanceTimeBy(150) // The dreaming signal is debounced.
-            runCurrent()
             keyguardRepository.setKeyguardOccluded(true)
-            powerInteractor.setAwakeForTest()
+            testScheduler.advanceTimeBy(150) // The dreaming and occluded signals are debounced.
             runCurrent()
 
             // THEN a transition to OCCLUDED should occur
@@ -2059,12 +2057,7 @@
     fun glanceableHubToOccluded_communalKtfRefactor() =
         testScope.runTest {
             // GIVEN device is not dreaming
-            powerInteractor.setAwakeForTest()
             keyguardRepository.setDreaming(false)
-            keyguardRepository.setDozeTransitionModel(
-                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
-            )
-            advanceTimeBy(600.milliseconds)
 
             // GIVEN a prior transition has run to GLANCEABLE_HUB
             communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
@@ -2073,6 +2066,7 @@
 
             // WHEN the keyguard is occluded
             keyguardRepository.setKeyguardOccluded(true)
+            advanceTimeBy(200.milliseconds)
             runCurrent()
 
             assertThat(transitionRepository)
@@ -2218,6 +2212,7 @@
             advanceTimeBy(10.milliseconds)
             keyguardRepository.setKeyguardOccluded(true)
             advanceTimeBy(200.milliseconds)
+            runCurrent()
 
             assertThat(transitionRepository)
                 .startedTransition(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
index f31eb7f..309e3a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -27,11 +27,18 @@
 import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.shared.model.TransitionStep
 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.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.data.repository.fakeShadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.domain.interactor.shadeLockscreenInteractor
@@ -62,6 +69,7 @@
     val kosmos = testKosmos()
     val testScope = kosmos.testScope
     val keyguardRepository = kosmos.fakeKeyguardRepository
+    val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
     val shadeRepository = kosmos.fakeShadeRepository
     val shadeTestUtil by lazy { kosmos.shadeTestUtil }
 
@@ -132,8 +140,15 @@
             assertThat(burnInOffsets?.x).isEqualTo(0)
 
             // WHEN we're in the middle of the doze amount change
-            keyguardRepository.setDozeAmount(.50f)
-            runCurrent()
+            if (SceneContainerFlag.isEnabled) {
+                sendTransitionSteps(
+                    TransitionStep(to = DOZING, value = 0.0f, transitionState = STARTED),
+                    TransitionStep(to = DOZING, value = 0.5f, transitionState = RUNNING),
+                )
+            } else {
+                keyguardRepository.setDozeAmount(.50f)
+                runCurrent()
+            }
 
             // THEN burn in is updated (between 0 and the full offset)
             assertThat(burnInOffsets?.progress).isGreaterThan(0f)
@@ -144,8 +159,14 @@
             assertThat(burnInOffsets?.x).isLessThan(burnInXOffset)
 
             // WHEN we're fully dozing
-            keyguardRepository.setDozeAmount(1f)
-            runCurrent()
+            if (SceneContainerFlag.isEnabled) {
+                sendTransitionSteps(
+                    TransitionStep(to = DOZING, value = 1.0f, transitionState = FINISHED)
+                )
+            } else {
+                keyguardRepository.setDozeAmount(1f)
+                runCurrent()
+            }
 
             // THEN burn in offsets are updated to final current values (for the given time)
             assertThat(burnInOffsets?.progress).isEqualTo(burnInProgress)
@@ -217,7 +238,9 @@
     }
 
     private fun setAwake() {
-        keyguardRepository.setDozeAmount(0f)
+        if (!SceneContainerFlag.isEnabled) {
+            keyguardRepository.setDozeAmount(0f)
+        }
         keyguardRepository.dozeTimeTick()
 
         bouncerRepository.setAlternateVisible(false)
@@ -225,4 +248,11 @@
         bouncerRepository.setPrimaryShow(false)
         powerInteractor.setAwakeForTest()
     }
+
+    private suspend fun sendTransitionSteps(vararg steps: TransitionStep) {
+        steps.forEach { step ->
+            keyguardTransitionRepository.sendTransitionStep(step)
+            testScope.runCurrent()
+        }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
index e58cf15..79a303d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
@@ -85,12 +85,12 @@
                 runCurrent()
 
                 // Assert that the tile is removed from the large tiles after resizing
-                underTest.resize(largeTile)
+                underTest.resize(largeTile, toIcon = true)
                 runCurrent()
                 assertThat(latest).doesNotContain(largeTile)
 
                 // Assert that the tile is added to the large tiles after resizing
-                underTest.resize(largeTile)
+                underTest.resize(largeTile, toIcon = false)
                 runCurrent()
                 assertThat(latest).contains(largeTile)
             }
@@ -122,7 +122,7 @@
                 val newTile = TileSpec.create("newTile")
 
                 // Remove the large tile from the current tiles
-                underTest.resize(newTile)
+                underTest.resize(newTile, toIcon = false)
                 runCurrent()
 
                 // Assert that it's still small
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
index 484a8ff..3910903 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
@@ -24,7 +24,6 @@
 import com.android.systemui.qs.panels.shared.model.SizedTile
 import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.ui.model.GridCell
-import com.android.systemui.qs.panels.ui.model.SpacerGridCell
 import com.android.systemui.qs.panels.ui.model.TileGridCell
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -39,13 +38,6 @@
     private val underTest = EditTileListState(TestEditTiles, 4)
 
     @Test
-    fun noDrag_listUnchanged() {
-        underTest.tiles.forEach { assertThat(it).isNotInstanceOf(SpacerGridCell::class.java) }
-        assertThat(underTest.tiles.map { (it as TileGridCell).tile.tileSpec })
-            .containsExactly(*TestEditTiles.map { it.tile.tileSpec }.toTypedArray())
-    }
-
-    @Test
     fun startDrag_listHasSpacers() {
         underTest.onStarted(TestEditTiles[0])
 
@@ -109,16 +101,6 @@
     }
 
     @Test
-    fun droppedNewTile_spacersDisappear() {
-        underTest.onStarted(TestEditTiles[0])
-        underTest.onDrop()
-
-        assertThat(underTest.tiles.toStrings()).isEqualTo(listOf("a", "b", "c", "d", "e"))
-        assertThat(underTest.isMoving(TestEditTiles[0].tile.tileSpec)).isFalse()
-        assertThat(underTest.dragInProgress).isFalse()
-    }
-
-    @Test
     fun movedTileOutOfBounds_tileDisappears() {
         underTest.onStarted(TestEditTiles[0])
         underTest.movedOutOfBounds()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt
index fa72d74..4acf3ee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt
@@ -27,23 +27,25 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class MutableSelectionStateTest : SysuiTestCase() {
-    private val underTest = MutableSelectionState()
+    private val underTest = MutableSelectionState({}, {})
 
     @Test
     fun selectTile_isCorrectlySelected() {
-        assertThat(underTest.isSelected(TEST_SPEC)).isFalse()
+        assertThat(underTest.selection?.tileSpec).isNotEqualTo(TEST_SPEC)
 
-        underTest.select(TEST_SPEC)
-        assertThat(underTest.isSelected(TEST_SPEC)).isTrue()
+        underTest.select(TEST_SPEC, manual = true)
+        assertThat(underTest.selection?.tileSpec).isEqualTo(TEST_SPEC)
+        assertThat(underTest.selection?.manual).isTrue()
 
         underTest.unSelect()
-        assertThat(underTest.isSelected(TEST_SPEC)).isFalse()
+        assertThat(underTest.selection).isNull()
 
         val newSpec = TileSpec.create("newSpec")
-        underTest.select(TEST_SPEC)
-        underTest.select(newSpec)
-        assertThat(underTest.isSelected(TEST_SPEC)).isFalse()
-        assertThat(underTest.isSelected(newSpec)).isTrue()
+        underTest.select(TEST_SPEC, manual = true)
+        underTest.select(newSpec, manual = false)
+        assertThat(underTest.selection?.tileSpec).isNotEqualTo(TEST_SPEC)
+        assertThat(underTest.selection?.tileSpec).isEqualTo(newSpec)
+        assertThat(underTest.selection?.manual).isFalse()
     }
 
     @Test
@@ -51,12 +53,12 @@
         assertThat(underTest.resizingState).isNull()
 
         // Resizing starts but no tile is selected
-        underTest.onResizingDragStart(TileWidths(0, 0, 1)) {}
+        underTest.onResizingDragStart(TileWidths(0, 0, 1))
         assertThat(underTest.resizingState).isNull()
 
         // Resizing starts with a selected tile
-        underTest.select(TEST_SPEC)
-        underTest.onResizingDragStart(TileWidths(0, 0, 1)) {}
+        underTest.select(TEST_SPEC, manual = true)
+        underTest.onResizingDragStart(TileWidths(0, 0, 1))
 
         assertThat(underTest.resizingState).isNotNull()
     }
@@ -66,8 +68,8 @@
         val spec = TileSpec.create("testSpec")
 
         // Resizing starts with a selected tile
-        underTest.select(spec)
-        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10)) {}
+        underTest.select(spec, manual = true)
+        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10))
         assertThat(underTest.resizingState).isNotNull()
 
         underTest.onResizingDragEnd()
@@ -77,8 +79,8 @@
     @Test
     fun unselect_clearsResizingState() {
         // Resizing starts with a selected tile
-        underTest.select(TEST_SPEC)
-        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10)) {}
+        underTest.select(TEST_SPEC, manual = true)
+        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10))
         assertThat(underTest.resizingState).isNotNull()
 
         underTest.unSelect()
@@ -88,8 +90,8 @@
     @Test
     fun onResizingDrag_updatesResizingState() {
         // Resizing starts with a selected tile
-        underTest.select(TEST_SPEC)
-        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10)) {}
+        underTest.select(TEST_SPEC, manual = true)
+        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10))
         assertThat(underTest.resizingState).isNotNull()
 
         underTest.onResizingDrag(5f)
@@ -105,11 +107,15 @@
     @Test
     fun onResizingDrag_receivesResizeCallback() {
         var resized = false
-        val onResize: () -> Unit = { resized = !resized }
+        val onResize: (TileSpec) -> Unit = {
+            assertThat(it).isEqualTo(TEST_SPEC)
+            resized = !resized
+        }
+        val underTest = MutableSelectionState(onResize = onResize, {})
 
         // Resizing starts with a selected tile
-        underTest.select(TEST_SPEC)
-        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10), onResize)
+        underTest.select(TEST_SPEC, true)
+        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10))
         assertThat(underTest.resizingState).isNotNull()
 
         // Drag under the threshold
@@ -125,6 +131,37 @@
         assertThat(resized).isFalse()
     }
 
+    @Test
+    fun onResizingEnded_receivesResizeEndCallback() {
+        var resizeEnded = false
+        val onResizeEnd: (TileSpec) -> Unit = { resizeEnded = true }
+        val underTest = MutableSelectionState({}, onResizeEnd = onResizeEnd)
+
+        // Resizing starts with a selected tile
+        underTest.select(TEST_SPEC, true)
+        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10))
+
+        underTest.onResizingDragEnd()
+        assertThat(resizeEnded).isTrue()
+    }
+
+    @Test
+    fun onResizingEnded_setsSelectionAutomatically() {
+        val underTest = MutableSelectionState({}, {})
+
+        // Resizing starts with a selected tile
+        underTest.select(TEST_SPEC, manual = true)
+        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10))
+
+        // Assert the selection was manual
+        assertThat(underTest.selection?.manual).isTrue()
+
+        underTest.onResizingDragEnd()
+
+        // Assert the selection is no longer manual due to the resizing
+        assertThat(underTest.selection?.manual).isFalse()
+    }
+
     companion object {
         private val TEST_SPEC = TileSpec.create("testSpec")
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index f72a2e8..aefbc6b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
@@ -107,6 +108,7 @@
                     uiEventLogger,
                     { kosmos.interactionJankMonitor },
                     JavaAdapter(testScope.backgroundScope),
+                    { kosmos.keyguardInteractor },
                     { kosmos.keyguardTransitionInteractor },
                     { kosmos.shadeInteractor },
                     { kosmos.deviceUnlockedInteractor },
@@ -139,6 +141,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     fun testSetDozeAmountInternal_onlySetsOnce() {
         val listener = mock(StatusBarStateController.StateListener::class.java)
         underTest.addCallback(listener)
@@ -190,6 +193,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     fun testSetDozeAmount_immediatelyChangesDozeAmount_lockscreenTransitionFromAod() {
         // Put controller in AOD state
         underTest.setAndInstrumentDozeAmount(null, 1f, false)
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 425f16e..4adf693 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
@@ -44,6 +44,7 @@
 import com.android.systemui.keyguard.shared.model.BurnInModel
 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -363,6 +364,69 @@
             showLockscreen()
             assertThat(alpha).isEqualTo(1f)
 
+            // Go to dozing
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = LOCKSCREEN,
+                to = DOZING,
+                testScope,
+            )
+            assertThat(alpha).isEqualTo(1f)
+
+            // Start transitioning to glanceable hub
+            val progress = 0.6f
+            kosmos.setTransition(
+                sceneTransition = Transition(from = Scenes.Lockscreen, to = Scenes.Communal),
+                stateTransition =
+                    TransitionStep(
+                        transitionState = TransitionState.STARTED,
+                        from = DOZING,
+                        to = GLANCEABLE_HUB,
+                        value = 0f,
+                    ),
+            )
+            runCurrent()
+            kosmos.setTransition(
+                sceneTransition =
+                    Transition(
+                        from = Scenes.Lockscreen,
+                        to = Scenes.Communal,
+                        progress = flowOf(progress),
+                    ),
+                stateTransition =
+                    TransitionStep(
+                        transitionState = TransitionState.RUNNING,
+                        from = DOZING,
+                        to = GLANCEABLE_HUB,
+                        value = progress,
+                    ),
+            )
+            runCurrent()
+            // Keep notifications hidden during the transition from dream to hub
+            assertThat(alpha).isEqualTo(0)
+
+            // Finish transition to glanceable hub
+            kosmos.setTransition(
+                sceneTransition = Idle(Scenes.Communal),
+                stateTransition =
+                    TransitionStep(
+                        transitionState = TransitionState.FINISHED,
+                        from = DOZING,
+                        to = GLANCEABLE_HUB,
+                        value = 1f,
+                    ),
+            )
+            assertThat(alpha).isEqualTo(0f)
+        }
+
+    @Test
+    fun glanceableHubAlpha_dozingToHub() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.glanceableHubAlpha)
+
+            // Start on lockscreen, notifications should be unhidden.
+            showLockscreen()
+            assertThat(alpha).isEqualTo(1f)
+
             // Transition to dream, notifications should be hidden so that transition
             // from dream->hub doesn't cause notification flicker.
             showDream()
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a3db776..24b6579 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1482,7 +1482,7 @@
     <!-- Text which is shown in the expanded notification shade when there are currently no notifications visible that the user hasn't already seen. [CHAR LIMIT=30] -->
     <string name="no_unseen_notif_text">No new notifications</string>
 
-    <!-- Title of heads up notification for adaptive notifications user education. [CHAR LIMIT=50] -->
+    <!-- Title of heads up notification for adaptive notifications user education. [CHAR LIMIT=60] -->
     <string name="adaptive_notification_edu_hun_title">Notification cooldown is on</string>
 
     <!-- Text of heads up notification for adaptive notifications user education. [CHAR LIMIT=100] -->
diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml
index 2f2b10f..66c54a3 100644
--- a/packages/SystemUI/res/xml/media_session_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_session_collapsed.xml
@@ -65,7 +65,7 @@
 
     <Constraint
         android:id="@+id/header_title"
-        android:layout_width="wrap_content"
+        android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_marginTop="20dp"
         android:layout_marginStart="@dimen/qs_media_padding"
@@ -87,13 +87,11 @@
         app:layout_constraintEnd_toStartOf="@id/header_artist"
         app:layout_constraintTop_toTopOf="@id/header_artist"
         app:layout_constraintBottom_toBottomOf="@id/header_artist"
-        app:layout_constraintVertical_bias="0"
-        app:layout_constraintHorizontal_bias="0"
-        app:layout_constraintHorizontal_chainStyle="packed" />
+        app:layout_constraintVertical_bias="0" />
 
     <Constraint
         android:id="@+id/header_artist"
-        android:layout_width="wrap_content"
+        android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_marginEnd="@dimen/qs_media_padding"
         android:layout_marginBottom="@dimen/qs_media_padding"
@@ -102,6 +100,8 @@
         app:layout_constrainedWidth="true"
         app:layout_constraintStart_toEndOf="@id/media_explicit_indicator"
         app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintHorizontal_weight="1"
+        app:layout_constraintHorizontal_chainStyle="spread_inside"
         app:layout_constraintVertical_bias="0" />
 
     <Constraint
diff --git a/packages/SystemUI/res/xml/media_session_expanded.xml b/packages/SystemUI/res/xml/media_session_expanded.xml
index 0140d52..19cc3bc5 100644
--- a/packages/SystemUI/res/xml/media_session_expanded.xml
+++ b/packages/SystemUI/res/xml/media_session_expanded.xml
@@ -58,7 +58,7 @@
 
     <Constraint
         android:id="@+id/header_title"
-        android:layout_width="wrap_content"
+        android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_marginStart="@dimen/qs_media_padding"
         android:layout_marginEnd="@dimen/qs_media_padding"
@@ -80,13 +80,11 @@
         app:layout_constraintStart_toStartOf="@id/header_title"
         app:layout_constraintEnd_toStartOf="@id/header_artist"
         app:layout_constraintTop_toTopOf="@id/header_artist"
-        app:layout_constraintBottom_toTopOf="@id/media_action_barrier_top"
-        app:layout_constraintHorizontal_bias="0"
-        app:layout_constraintHorizontal_chainStyle="packed"/>
+        app:layout_constraintBottom_toTopOf="@id/media_action_barrier_top" />
 
     <Constraint
         android:id="@+id/header_artist"
-        android:layout_width="wrap_content"
+        android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_marginEnd="@dimen/qs_media_padding"
         android:layout_marginBottom="@dimen/qs_media_padding"
@@ -95,6 +93,8 @@
         app:layout_constraintEnd_toStartOf="@id/actionPlayPause"
         app:layout_constraintStart_toEndOf="@id/media_explicit_indicator"
         app:layout_constraintBottom_toTopOf="@id/media_action_barrier_top"
+        app:layout_constraintHorizontal_weight="1"
+        app:layout_constraintHorizontal_chainStyle="spread_inside"
         app:layout_constraintVertical_bias="0" />
 
     <Constraint
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index 873d1b3..4185aed 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -86,6 +86,9 @@
 
             _animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED
             clearInput()
+            if (authenticationResult == AuthenticationResult.SUCCEEDED) {
+                onSuccessfulAuthentication()
+            }
         }
         awaitCancellation()
     }
@@ -116,6 +119,9 @@
     /** Returns the input entered so far. */
     protected abstract fun getInput(): List<Any>
 
+    /** Invoked after a successful authentication. */
+    protected open fun onSuccessfulAuthentication() = Unit
+
     /** Perform authentication result haptics */
     private fun performAuthenticationHapticFeedback(result: AuthenticationResult) {
         if (result == AuthenticationResult.SKIPPED) return
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 2493cf1..1427d78 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -81,50 +81,59 @@
     val selectedUserId: StateFlow<Int> = _selectedUserId.asStateFlow()
 
     private val requests = Channel<Request>(Channel.BUFFERED)
+    private var wasSuccessfullyAuthenticated = false
 
     override suspend fun onActivated(): Nothing {
-        coroutineScope {
-            launch { super.onActivated() }
-            launch {
-                requests.receiveAsFlow().collect { request ->
-                    when (request) {
-                        is OnImeSwitcherButtonClicked -> {
-                            inputMethodInteractor.showInputMethodPicker(
-                                displayId = request.displayId,
-                                showAuxiliarySubtypes = false,
-                            )
-                        }
-                        is OnImeDismissed -> {
-                            interactor.onImeHiddenByUser()
+        try {
+            coroutineScope {
+                launch { super.onActivated() }
+                launch {
+                    requests.receiveAsFlow().collect { request ->
+                        when (request) {
+                            is OnImeSwitcherButtonClicked -> {
+                                inputMethodInteractor.showInputMethodPicker(
+                                    displayId = request.displayId,
+                                    showAuxiliarySubtypes = false,
+                                )
+                            }
+                            is OnImeDismissed -> {
+                                interactor.onImeHiddenByUser()
+                            }
                         }
                     }
                 }
+                launch {
+                    combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus ->
+                            hasInput && !hasFocus && !wasSuccessfullyAuthenticated
+                        }
+                        .collect { _isTextFieldFocusRequested.value = it }
+                }
+                launch {
+                    selectedUserInteractor.selectedUser.collect { _selectedUserId.value = it }
+                }
+                launch {
+                    // Re-fetch the currently-enabled IMEs whenever the selected user changes, and
+                    // whenever
+                    // the UI subscribes to the `isImeSwitcherButtonVisible` flow.
+                    combine(
+                            // InputMethodManagerService sometimes takes
+                            // some time to update its internal state when the
+                            // selected user changes.
+                            // As a workaround, delay fetching the IME info.
+                            selectedUserInteractor.selectedUser.onEach {
+                                delay(DELAY_TO_FETCH_IMES)
+                            },
+                            _isImeSwitcherButtonVisible.onSubscriberAdded(),
+                        ) { selectedUserId, _ ->
+                            inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId)
+                        }
+                        .collect { _isImeSwitcherButtonVisible.value = it }
+                }
+                awaitCancellation()
             }
-            launch {
-                combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus ->
-                        hasInput && !hasFocus
-                    }
-                    .collect { _isTextFieldFocusRequested.value = it }
-            }
-            launch { selectedUserInteractor.selectedUser.collect { _selectedUserId.value = it } }
-            launch {
-                // Re-fetch the currently-enabled IMEs whenever the selected user changes, and
-                // whenever
-                // the UI subscribes to the `isImeSwitcherButtonVisible` flow.
-                combine(
-                        // InputMethodManagerService sometimes takes some time to update its
-                        // internal
-                        // state when the selected user changes. As a workaround, delay fetching the
-                        // IME
-                        // info.
-                        selectedUserInteractor.selectedUser.onEach { delay(DELAY_TO_FETCH_IMES) },
-                        _isImeSwitcherButtonVisible.onSubscriberAdded()
-                    ) { selectedUserId, _ ->
-                        inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId)
-                    }
-                    .collect { _isImeSwitcherButtonVisible.value = it }
-            }
-            awaitCancellation()
+        } finally {
+            // reset whenever the view model is "deactivated"
+            wasSuccessfullyAuthenticated = false
         }
     }
 
@@ -141,6 +150,10 @@
         return _password.value.toCharArray().toList()
     }
 
+    override fun onSuccessfulAuthentication() {
+        wasSuccessfullyAuthenticated = true
+    }
+
     /** Notifies that the user has changed the password input. */
     fun onPasswordInputChanged(newPassword: String) {
         if (newPassword.isNotEmpty()) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
index 12597a7..99c026c 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
@@ -24,6 +24,7 @@
 import android.util.Log
 import android.util.Size
 import android.view.textclassifier.TextLinks
+import com.android.systemui.Flags.clipboardUseDescriptionMimetype
 import com.android.systemui.res.R
 import java.io.IOException
 
@@ -70,11 +71,11 @@
             context: Context,
             utils: ClipboardOverlayUtils,
             clipData: ClipData,
-            source: String
+            source: String,
         ): ClipboardModel {
             val sensitive = clipData.description?.extras?.getBoolean(EXTRA_IS_SENSITIVE) ?: false
             val item = clipData.getItemAt(0)!!
-            val type = getType(context, item)
+            val type = getType(context, item, clipData.description.getMimeType(0))
             val remote = utils.isRemoteCopy(context, clipData, source)
             return ClipboardModel(
                 clipData,
@@ -84,18 +85,26 @@
                 item.textLinks,
                 item.uri,
                 sensitive,
-                remote
+                remote,
             )
         }
 
-        private fun getType(context: Context, item: ClipData.Item): Type {
+        private fun getType(context: Context, item: ClipData.Item, mimeType: String): Type {
             return if (!TextUtils.isEmpty(item.text)) {
                 Type.TEXT
             } else if (item.uri != null) {
-                if (context.contentResolver.getType(item.uri)?.startsWith("image") == true) {
-                    Type.IMAGE
+                if (clipboardUseDescriptionMimetype()) {
+                    if (mimeType.startsWith("image")) {
+                        Type.IMAGE
+                    } else {
+                        Type.URI
+                    }
                 } else {
-                    Type.URI
+                    if (context.contentResolver.getType(item.uri)?.startsWith("image") == true) {
+                        Type.IMAGE
+                    } else {
+                        Type.URI
+                    }
                 }
             } else {
                 Type.OTHER
@@ -107,6 +116,6 @@
         TEXT,
         IMAGE,
         URI,
-        OTHER
+        OTHER,
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
index 04393fe..1bd541e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
@@ -65,7 +65,9 @@
                 keyguardTransitionInteractor
                     .transitionValue(Scenes.Communal, KeyguardState.GLANCEABLE_HUB)
                     .map { it == 1f },
-                not(keyguardInteractor.isDreaming),
+                // Use isDreamingAny because isDreaming is false in doze and doesn't change again
+                // when the screen turns on, which causes the dream to not start underneath the hub.
+                not(keyguardInteractor.isDreamingAny),
                 // TODO(b/362830856): Remove this workaround.
                 keyguardInteractor.isKeyguardShowing,
                 not(communalSceneInteractor.isLaunchingWidget),
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt
index f77dd58..f0f7ca5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt
@@ -82,19 +82,26 @@
             }
 
         _timers.value =
-            timerTargets.map { (stableId, target) ->
-                CommunalSmartspaceTimer(
-                    // The view layer should have the instance based smartspaceTargetId instead of
-                    // stable id, so that when a new instance of the timer is created, for example,
-                    // when it is paused, the view should re-render its remote views.
-                    smartspaceTargetId =
-                        if (communalTimerFlickerFix()) stableId else target.smartspaceTargetId,
-                    createdTimestampMillis = targetCreationTimes[stableId]!!,
-                    remoteViews = target.remoteViews!!,
-                )
-            }
-
-        logger.d({ "Smartspace timers updated: $str1" }) { str1 = _timers.value.toString() }
+            timerTargets
+                .map { (stableId, target) ->
+                    CommunalSmartspaceTimer(
+                        // The view layer should have the instance based smartspaceTargetId instead
+                        // of stable id, so that when a new instance of the timer is created, for
+                        // example, when it is paused, the view should re-render its remote views.
+                        smartspaceTargetId =
+                            if (communalTimerFlickerFix()) stableId else target.smartspaceTargetId,
+                        createdTimestampMillis = targetCreationTimes[stableId]!!,
+                        remoteViews = target.remoteViews!!,
+                    )
+                }
+                .also { newVal ->
+                    // Only log when value changes to avoid filling up the buffer.
+                    if (newVal != _timers.value) {
+                        logger.d({ "Smartspace timers updated: $str1" }) {
+                            str1 = newVal.toString()
+                        }
+                    }
+                }
     }
 
     override fun startListening() {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
index c7538bb4..905eda1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
@@ -87,7 +87,9 @@
      */
     private val nextKeyguardStateInternal =
         combine(
-                keyguardInteractor.isAbleToDream,
+                // Don't use delayed dreaming signal as otherwise we might go to occluded or lock
+                // screen when closing hub if dream just started under the hub.
+                keyguardInteractor.isDreamingWithOverlay,
                 keyguardInteractor.isKeyguardOccluded,
                 keyguardInteractor.isKeyguardGoingAway,
                 keyguardInteractor.isKeyguardShowing,
@@ -156,7 +158,7 @@
 
     private suspend fun handleIdle(
         prevTransition: ObservableTransitionState,
-        idle: ObservableTransitionState.Idle
+        idle: ObservableTransitionState.Idle,
     ) {
         if (
             prevTransition is ObservableTransitionState.Transition &&
@@ -186,7 +188,7 @@
         internalTransitionInteractor.updateTransition(
             currentTransitionId!!,
             1f,
-            TransitionState.FINISHED
+            TransitionState.FINISHED,
         )
         resetTransitionData()
     }
@@ -204,7 +206,7 @@
         internalTransitionInteractor.updateTransition(
             currentTransitionId!!,
             1f,
-            TransitionState.FINISHED
+            TransitionState.FINISHED,
         )
         resetTransitionData()
     }
@@ -217,7 +219,7 @@
 
     private suspend fun handleTransition(
         prevTransition: ObservableTransitionState,
-        transition: ObservableTransitionState.Transition
+        transition: ObservableTransitionState.Transition,
     ) {
         if (
             prevTransition.isTransitioning(from = transition.fromContent, to = transition.toContent)
@@ -295,7 +297,7 @@
         internalTransitionInteractor.updateTransition(
             currentTransitionId!!,
             progress.coerceIn(0f, 1f),
-            TransitionState.RUNNING
+            TransitionState.RUNNING,
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt
index 7453368..f7cd2ab 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt
@@ -16,10 +16,13 @@
 
 package com.android.systemui.communal.domain.interactor
 
+import android.annotation.SuppressLint
 import android.app.ActivityManager
+import android.app.DreamManager
 import com.android.systemui.common.usagestats.domain.UsageStatsInteractor
 import com.android.systemui.common.usagestats.shared.model.ActivityEventModel
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.log.LogBuffer
@@ -34,10 +37,12 @@
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.TimeoutCancellationException
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withTimeout
 
@@ -56,6 +61,8 @@
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val taskStackChangeListeners: TaskStackChangeListeners,
     private val usageStatsInteractor: UsageStatsInteractor,
+    private val dreamManager: DreamManager,
+    @Background private val bgScope: CoroutineScope,
     @CommunalLog logBuffer: LogBuffer,
 ) {
     private companion object {
@@ -127,13 +134,21 @@
      * Checks if an activity starts while on the glanceable hub and dismisses the keyguard if it
      * does. This can detect activities started due to broadcast trampolines from widgets.
      */
+    @SuppressLint("MissingPermission")
     suspend fun waitForActivityStartAndDismissKeyguard() {
         if (waitForActivityStartWhileOnHub()) {
             logger.d("Detected trampoline, requesting unlock")
             activityStarter.dismissKeyguardThenExecute(
-                /* action= */ { false },
+                /* action= */ {
+                    // Kill the dream when launching the trampoline activity. Right now the exit
+                    // animation stalls when tapping the battery widget, and the dream remains
+                    // visible until the transition hits some timeouts and gets cancelled.
+                    // TODO(b/362841648): remove once exit animation is fixed.
+                    bgScope.launch { dreamManager.stopDream() }
+                    false
+                },
                 /* cancel= */ null,
-                /* afterKeyguardGone= */ false
+                /* afterKeyguardGone= */ false,
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index 28db3b8..f90f02a 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -70,7 +70,14 @@
 ) {
     private val keyguardOccludedByApp: Flow<Boolean> =
         if (KeyguardWmStateRefactor.isEnabled) {
-            keyguardTransitionInteractor.currentKeyguardState.map { it == KeyguardState.OCCLUDED }
+            combine(
+                    keyguardTransitionInteractor.currentKeyguardState,
+                    communalSceneInteractor.isIdleOnCommunal,
+                    ::Pair,
+                )
+                .map { (currentState, isIdleOnCommunal) ->
+                    currentState == KeyguardState.OCCLUDED && !isIdleOnCommunal
+                }
         } else {
             combine(
                     keyguardInteractor.isKeyguardOccluded,
@@ -120,7 +127,7 @@
             // On fingerprint success when the screen is on and not dreaming, go to the home screen
             fingerprintUnlockSuccessEvents
                 .sample(
-                    combine(powerInteractor.isInteractive, keyguardInteractor.isDreaming, ::Pair),
+                    combine(powerInteractor.isInteractive, keyguardInteractor.isDreaming, ::Pair)
                 )
                 .collect { (interactive, dreaming) ->
                     if (interactive && !dreaming) {
@@ -148,7 +155,7 @@
                         }
                     },
                     /* cancel= */ null,
-                    /* afterKeyguardGone */ false
+                    /* afterKeyguardGone */ false,
                 )
             }
         }
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 77c54ec..3992c3f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
@@ -39,6 +39,7 @@
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 
 class HomeControlsDreamService
 @Inject
@@ -53,7 +54,7 @@
 ) : DreamService() {
 
     private val serviceJob = SupervisorJob()
-    private val serviceScope = CoroutineScope(bgDispatcher + serviceJob)
+    private val serviceScope = CoroutineScope(bgDispatcher + serviceJob + createCoroutineTracingContext("HomeControlsDreamService"))
     private val logger = DreamLogger(logBuffer, TAG)
     private lateinit var taskFragmentComponent: TaskFragmentComponent
     private val wakeLock: WakeLock by lazy {
diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
index 7e2c9f8..4caf95b 100644
--- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.education.dagger
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import com.android.systemui.CoreStartable
 import com.android.systemui.Flags
 import com.android.systemui.contextualeducation.GestureType
@@ -56,7 +57,7 @@
         fun provideEduDataStoreScope(
             @Background bgDispatcher: CoroutineDispatcher
         ): CoroutineScope {
-            return CoroutineScope(bgDispatcher + SupervisorJob())
+            return CoroutineScope(bgDispatcher + SupervisorJob() + createCoroutineTracingContext("EduDataStoreScope"))
         }
 
         @EduClock
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
index b271356..f32c94b 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
@@ -22,6 +22,7 @@
 import androidx.annotation.RawRes
 import androidx.annotation.StringRes
 import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.EnterTransition
 import androidx.compose.animation.ExitTransition
 import androidx.compose.animation.core.LinearEasing
@@ -40,6 +41,7 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawingPadding
 import androidx.compose.foundation.layout.width
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
@@ -67,21 +69,22 @@
 enum class TutorialActionState {
     NOT_STARTED,
     IN_PROGRESS,
-    FINISHED
+    FINISHED,
 }
 
 @Composable
 fun ActionTutorialContent(
     actionState: TutorialActionState,
     onDoneButtonClicked: () -> Unit,
-    config: TutorialScreenConfig
+    config: TutorialScreenConfig,
 ) {
     Column(
         verticalArrangement = Arrangement.Center,
         modifier =
             Modifier.fillMaxSize()
                 .background(config.colors.background)
-                .padding(start = 48.dp, top = 124.dp, end = 48.dp, bottom = 48.dp)
+                .safeDrawingPadding()
+                .padding(start = 48.dp, top = 100.dp, end = 48.dp, bottom = 8.dp),
     ) {
         Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
             TutorialDescription(
@@ -92,16 +95,18 @@
                 bodyTextId =
                     if (actionState == FINISHED) config.strings.bodySuccessResId
                     else config.strings.bodyResId,
-                modifier = Modifier.weight(1f)
+                modifier = Modifier.weight(1f),
             )
             Spacer(modifier = Modifier.width(76.dp))
             TutorialAnimation(
                 actionState,
                 config,
-                modifier = Modifier.weight(1f).padding(top = 8.dp)
+                modifier = Modifier.weight(1f).padding(top = 8.dp),
             )
         }
-        DoneButton(onDoneButtonClicked = onDoneButtonClicked)
+        AnimatedVisibility(visible = actionState == FINISHED, enter = fadeIn()) {
+            DoneButton(onDoneButtonClicked = onDoneButtonClicked)
+        }
     }
 }
 
@@ -110,19 +115,19 @@
     @StringRes titleTextId: Int,
     titleColor: Color,
     @StringRes bodyTextId: Int,
-    modifier: Modifier = Modifier
+    modifier: Modifier = Modifier,
 ) {
     Column(verticalArrangement = Arrangement.Top, modifier = modifier) {
         Text(
             text = stringResource(id = titleTextId),
             style = MaterialTheme.typography.displayLarge,
-            color = titleColor
+            color = titleColor,
         )
         Spacer(modifier = Modifier.height(16.dp))
         Text(
             text = stringResource(id = bodyTextId),
             style = MaterialTheme.typography.bodyLarge,
-            color = Color.White
+            color = Color.White,
         )
     }
 }
@@ -131,7 +136,7 @@
 fun TutorialAnimation(
     actionState: TutorialActionState,
     config: TutorialScreenConfig,
-    modifier: Modifier = Modifier
+    modifier: Modifier = Modifier,
 ) {
     Box(modifier = modifier.fillMaxWidth()) {
         AnimatedContent(
@@ -152,18 +157,18 @@
                     // state which shares initial animation frame with both FINISHED and NOT_STARTED
                     EnterTransition.None togetherWith ExitTransition.None
                 }
-            }
+            },
         ) { state ->
             when (state) {
                 NOT_STARTED ->
                     EducationAnimation(
                         config.animations.educationResId,
-                        config.colors.animationColors
+                        config.colors.animationColors,
                     )
                 IN_PROGRESS ->
                     FrozenSuccessAnimation(
                         config.animations.successResId,
-                        config.colors.animationColors
+                        config.colors.animationColors,
                     )
                 FINISHED ->
                     SuccessAnimation(config.animations.successResId, config.colors.animationColors)
@@ -175,7 +180,7 @@
 @Composable
 private fun FrozenSuccessAnimation(
     @RawRes successAnimationId: Int,
-    animationProperties: LottieDynamicProperties
+    animationProperties: LottieDynamicProperties,
 ) {
     val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId))
     LottieAnimation(
@@ -188,7 +193,7 @@
 @Composable
 private fun EducationAnimation(
     @RawRes educationAnimationId: Int,
-    animationProperties: LottieDynamicProperties
+    animationProperties: LottieDynamicProperties,
 ) {
     val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(educationAnimationId))
     val progress by
@@ -203,7 +208,7 @@
 @Composable
 private fun SuccessAnimation(
     @RawRes successAnimationId: Int,
-    animationProperties: LottieDynamicProperties
+    animationProperties: LottieDynamicProperties,
 ) {
     val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId))
     val progress by animateLottieCompositionAsState(composition, iterations = 1)
@@ -217,13 +222,13 @@
 @Composable
 fun rememberColorFilterProperty(
     layerName: String,
-    color: Color
+    color: Color,
 ): LottieDynamicProperty<ColorFilter> {
     return rememberLottieDynamicProperty(
         LottieProperty.COLOR_FILTER,
         value = PorterDuffColorFilter(color.toArgb(), PorterDuff.Mode.SRC_ATOP),
         // "**" below means match zero or more layers, so ** layerName ** means find layer with that
         // name at any depth
-        keyPath = arrayOf("**", layerName, "**")
+        keyPath = arrayOf("**", layerName, "**"),
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index ba23eb3..0a38ce0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -3314,7 +3314,11 @@
 
         setShowingLocked(false, "onKeyguardExitFinished: " + reason);
         mWakeAndUnlocking = false;
-        mDismissCallbackRegistry.notifyDismissSucceeded();
+
+        if (!KeyguardWmStateRefactor.isEnabled()) {
+            mDismissCallbackRegistry.notifyDismissSucceeded();
+        }
+
         resetKeyguardDonePendingLocked();
         mHideAnimationRun = false;
         adjustStatusBarLocked();
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 49303e0..130242f 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
@@ -58,6 +58,7 @@
 import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.mapLatest
@@ -486,19 +487,34 @@
 
     override val isDreaming: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
-    override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow {
-        val callback =
-            object : StatusBarStateController.StateListener {
-                override fun onDozeAmountChanged(linear: Float, eased: Float) {
-                    trySendWithFailureLogging(linear, TAG, "updated dozeAmount")
-                }
+    private val _preSceneLinearDozeAmount: Flow<Float> =
+        if (SceneContainerFlag.isEnabled) {
+            emptyFlow()
+        } else {
+            conflatedCallbackFlow {
+                val callback =
+                    object : StatusBarStateController.StateListener {
+                        override fun onDozeAmountChanged(linear: Float, eased: Float) {
+                            trySendWithFailureLogging(linear, TAG, "updated dozeAmount")
+                        }
+                    }
+
+                statusBarStateController.addCallback(callback)
+                trySendWithFailureLogging(
+                    statusBarStateController.dozeAmount,
+                    TAG,
+                    "initial dozeAmount"
+                )
+
+                awaitClose { statusBarStateController.removeCallback(callback) }
             }
+        }
 
-        statusBarStateController.addCallback(callback)
-        trySendWithFailureLogging(statusBarStateController.dozeAmount, TAG, "initial dozeAmount")
-
-        awaitClose { statusBarStateController.removeCallback(callback) }
-    }
+    override val linearDozeAmount: Flow<Float>
+        get() {
+            SceneContainerFlag.assertInLegacyMode()
+            return _preSceneLinearDozeAmount
+        }
 
     override val dozeTransitionModel: Flow<DozeTransitionModel> = conflatedCallbackFlow {
         val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index b0820a7..8c7fe5f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -150,7 +150,9 @@
                         if (!SceneContainerFlag.isEnabled) {
                             startTransitionTo(KeyguardState.GLANCEABLE_HUB)
                         }
-                    } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) {
+                    } else if (isCommunalAvailable && dreamManager.canStartDreaming(false)) {
+                        // Using false for isScreenOn as canStartDreaming returns false if any
+                        // dream, including doze, is active.
                         // This case handles tapping the power button to transition through
                         // dream -> off -> hub.
                         if (!SceneContainerFlag.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 2434b29..9a0a858 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -17,9 +17,12 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.app.DreamManager
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launch
 import com.android.systemui.Flags.communalSceneKtfRefactor
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.communal.shared.model.CommunalScenes
@@ -60,10 +63,12 @@
     @Main mainDispatcher: CoroutineDispatcher,
     keyguardInteractor: KeyguardInteractor,
     private val glanceableHubTransitions: GlanceableHubTransitions,
+    private val communalInteractor: CommunalInteractor,
     private val communalSceneInteractor: CommunalSceneInteractor,
     private val communalSettingsInteractor: CommunalSettingsInteractor,
     powerInteractor: PowerInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+    private val dreamManager: DreamManager,
     private val deviceEntryInteractor: DeviceEntryInteractor,
 ) :
     TransitionInteractor(
@@ -76,6 +81,7 @@
         keyguardInteractor = keyguardInteractor,
     ) {
 
+    @SuppressLint("MissingPermission")
     override fun start() {
         listenForDreamingToAlternateBouncer()
         listenForDreamingToOccluded()
@@ -86,6 +92,8 @@
         listenForTransitionToCamera(scope, keyguardInteractor)
         if (!communalSceneKtfRefactor()) {
             listenForDreamingToGlanceableHub()
+        } else {
+            listenForDreamingToGlanceableHubFromPowerButton()
         }
         listenForDreamingToPrimaryBouncer()
     }
@@ -112,6 +120,34 @@
         }
     }
 
+    /**
+     * Normally when pressing power button from the dream, the devices goes from DREAMING to DOZING,
+     * then [FromDozingTransitionInteractor] handles the transition to GLANCEABLE_HUB. However if
+     * the power button is pressed quickly, we may need to go directly from DREAMING to
+     * GLANCEABLE_HUB as the transition to DOZING has not occurred yet.
+     */
+    @SuppressLint("MissingPermission")
+    private fun listenForDreamingToGlanceableHubFromPowerButton() {
+        if (!communalSettingsInteractor.isCommunalFlagEnabled()) return
+        if (SceneContainerFlag.isEnabled) return
+        scope.launch {
+            powerInteractor.isAwake
+                .debounce(50L)
+                .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
+                .sample(communalInteractor.isCommunalAvailable)
+                .collect { isCommunalAvailable ->
+                    if (isCommunalAvailable && dreamManager.canStartDreaming(false)) {
+                        // This case handles tapping the power button to transition through
+                        // dream -> off -> hub.
+                        communalSceneInteractor.snapToScene(
+                            newScene = CommunalScenes.Communal,
+                            loggingReason = "from dreaming to hub",
+                        )
+                    }
+                }
+        }
+    }
+
     private fun listenForDreamingToPrimaryBouncer() {
         // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
@@ -144,7 +180,7 @@
                     } else {
                         startTransitionTo(
                             KeyguardState.LOCKSCREEN,
-                            ownerReason = "Dream has ended and device is awake"
+                            ownerReason = "Dream has ended and device is awake",
                         )
                     }
                 }
@@ -158,15 +194,14 @@
             scope.launch {
                 combine(
                         keyguardInteractor.isKeyguardOccluded,
-                        keyguardInteractor.isAbleToDream
-                            // Debounce the dreaming signal since there is a race condition between
-                            // the occluded and dreaming signals. We therefore add a small delay
-                            // to give enough time for occluded to flip to false when the dream
-                            // ends, to avoid transitioning to OCCLUDED erroneously when exiting
-                            // the dream.
-                            .debounce(100.milliseconds),
-                        ::Pair
+                        keyguardInteractor.isDreaming,
+                        ::Pair,
                     )
+                    // Debounce signals since there is a race condition between the occluded and
+                    // dreaming signals when starting or stopping dreaming. We therefore add a small
+                    // delay to give enough time for occluded to flip to false when the dream
+                    // ends, to avoid transitioning to OCCLUDED erroneously when exiting the dream.
+                    .debounce(100.milliseconds)
                     .filterRelevantKeyguardStateAnd { (isOccluded, isDreaming) ->
                         isOccluded && !isDreaming
                     }
@@ -194,12 +229,12 @@
                     if (dismissable) {
                         startTransitionTo(
                             KeyguardState.GONE,
-                            ownerReason = "No longer dreaming; dismissable"
+                            ownerReason = "No longer dreaming; dismissable",
                         )
                     } else {
                         startTransitionTo(
                             KeyguardState.LOCKSCREEN,
-                            ownerReason = "No longer dreaming"
+                            ownerReason = "No longer dreaming",
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 7759298..6b6a3dce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -202,15 +202,15 @@
             scope.launch {
                 combine(
                         keyguardInteractor.isKeyguardOccluded,
-                        keyguardInteractor.isAbleToDream
-                            // Debounce the dreaming signal since there is a race condition between
-                            // the occluded and dreaming signals. We therefore add a small delay
-                            // to give enough time for occluded to flip to false when the dream
-                            // ends, to avoid transitioning to OCCLUDED erroneously when exiting
-                            // the dream.
-                            .debounce(100.milliseconds),
+                        keyguardInteractor.isDreaming,
                         ::Pair,
                     )
+                    // Debounce signals since there is a race condition between the occluded and
+                    // dreaming signals when starting or stopping dreaming. We therefore add a small
+                    // delay to give enough time for occluded to flip to false when the dream
+                    // ends, to avoid transitioning to OCCLUDED erroneously when exiting the dream
+                    // or when the dream starts underneath the hub.
+                    .debounce(200.milliseconds)
                     .sampleFilter(
                         // When launching activities from widgets on the hub, we have a
                         // custom occlusion animation.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
index eb9b07a..ea80911 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
@@ -65,6 +65,7 @@
     powerInteractor: PowerInteractor,
     alternateBouncerInteractor: AlternateBouncerInteractor,
     shadeInteractor: Lazy<ShadeInteractor>,
+    keyguardInteractor: Lazy<KeyguardInteractor>,
 ) {
     val dismissAction: Flow<DismissAction> = repository.dismissAction
 
@@ -111,9 +112,9 @@
         } else if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) {
             combine(
                 shadeInteractor.get().isAnyExpanded,
-                deviceUnlockedInteractor.get().deviceUnlockStatus,
-            ) { isAnyExpanded, deviceUnlockStatus ->
-                isAnyExpanded && deviceUnlockStatus.isUnlocked
+                keyguardInteractor.get().isKeyguardDismissible,
+            ) { isAnyExpanded, keyguardDismissible ->
+                isAnyExpanded && keyguardDismissible
             }
         } else {
             flow {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
index 4457f1d..9b9bdd1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
@@ -23,12 +23,10 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.keyguard.shared.model.DismissAction
 import com.android.systemui.keyguard.shared.model.KeyguardDone
-import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
@@ -37,7 +35,6 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
@@ -59,7 +56,6 @@
     trustRepository: TrustRepository,
     alternateBouncerInteractor: AlternateBouncerInteractor,
     powerInteractor: PowerInteractor,
-    keyguardTransitionInteractor: KeyguardTransitionInteractor,
 ) {
     /*
      * Updates when a biometric has authenticated the device and is requesting to dismiss
@@ -165,14 +161,4 @@
             }
         }
     }
-
-    init {
-        if (KeyguardWmStateRefactor.isEnabled) {
-            scope.launch {
-                keyguardTransitionInteractor.currentKeyguardState
-                    .filter { it == KeyguardState.GONE }
-                    .collect { dismissCallbackRegistry.notifyDismissSucceeded() }
-            }
-        }
-    }
 }
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 e444092..e6ee112 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
@@ -140,12 +140,6 @@
         _notificationPlaceholderBounds.value = position
     }
 
-    /**
-     * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
-     * all.
-     */
-    val dozeAmount: Flow<Float> = repository.linearDozeAmount
-
     /** Whether the system is in doze mode. */
     val isDozing: StateFlow<Boolean> = repository.isDozing
 
@@ -155,6 +149,23 @@
     /** Whether Always-on Display mode is available. */
     val isAodAvailable: StateFlow<Boolean> = repository.isAodAvailable
 
+    /**
+     * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
+     * all.
+     */
+    val dozeAmount: Flow<Float> =
+        if (SceneContainerFlag.isEnabled) {
+            isAodAvailable.flatMapLatest { isAodAvailable ->
+                if (isAodAvailable) {
+                    keyguardTransitionInteractor.transitionValue(AOD)
+                } else {
+                    keyguardTransitionInteractor.transitionValue(DOZING)
+                }
+            }
+        } else {
+            repository.linearDozeAmount
+        }
+
     /** Doze transition information. */
     val dozeTransitionModel: Flow<DozeTransitionModel> = repository.dozeTransitionModel
 
@@ -164,8 +175,8 @@
     val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
 
     /**
-     * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true,
-     * but not vice-versa. Also accounts for [isDreamingWithOverlay]
+     * Whether the system is dreaming. [KeyguardRepository.isDreaming] will be always be true when
+     * [isDozing] is true, but not vice-versa. Also accounts for [isDreamingWithOverlay].
      */
     val isDreaming: StateFlow<Boolean> =
         merge(repository.isDreaming, repository.isDreamingWithOverlay)
@@ -175,6 +186,9 @@
                 initialValue = false,
             )
 
+    /** Whether any dreaming is running, including the doze dream. */
+    val isDreamingAny: Flow<Boolean> = repository.isDreaming
+
     /** Whether the system is dreaming and the active dream is hosted in lockscreen */
     val isActiveDreamLockscreenHosted: StateFlow<Boolean> = repository.isActiveDreamLockscreenHosted
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt
index 7fd348b..6fe4ff5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.app.trust.TrustManager
 import android.os.DeadObjectException
 import android.os.RemoteException
 import com.android.internal.policy.IKeyguardStateCallback
@@ -24,6 +25,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -32,7 +34,6 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
@@ -53,6 +54,9 @@
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val trustInteractor: TrustInteractor,
     private val simBouncerInteractor: SimBouncerInteractor,
+    private val dismissCallbackRegistry: DismissCallbackRegistry,
+    private val wmLockscreenVisibilityInteractor: WindowManagerLockscreenVisibilityInteractor,
+    private val trustManager: TrustManager,
 ) : CoreStartable {
     private val callbacks = mutableListOf<IKeyguardStateCallback>()
 
@@ -62,28 +66,31 @@
         }
 
         applicationScope.launch {
-            combine(
-                    selectedUserInteractor.selectedUser,
-                    keyguardTransitionInteractor.currentKeyguardState,
-                    keyguardTransitionInteractor.startedKeyguardTransitionStep,
-                    ::Triple,
-                )
-                .collectLatest { (selectedUser, _, _) ->
-                    val iterator = callbacks.iterator()
-                    withContext(backgroundDispatcher) {
-                        while (iterator.hasNext()) {
-                            val callback = iterator.next()
-                            try {
-                                callback.onShowingStateChanged(!isIdleInGone(), selectedUser)
-                                callback.onInputRestrictedStateChanged(!isIdleInGone())
-                            } catch (e: RemoteException) {
-                                if (e is DeadObjectException) {
-                                    iterator.remove()
-                                }
+            wmLockscreenVisibilityInteractor.lockscreenVisibility.collectLatest { visible ->
+                val iterator = callbacks.iterator()
+                withContext(backgroundDispatcher) {
+                    while (iterator.hasNext()) {
+                        val callback = iterator.next()
+                        try {
+                            callback.onShowingStateChanged(
+                                visible,
+                                selectedUserInteractor.getSelectedUserId(),
+                            )
+                            callback.onInputRestrictedStateChanged(visible)
+
+                            trustManager.reportKeyguardShowingChanged()
+
+                            if (!visible) {
+                                dismissCallbackRegistry.notifyDismissSucceeded()
+                            }
+                        } catch (e: RemoteException) {
+                            if (e is DeadObjectException) {
+                                iterator.remove()
                             }
                         }
                     }
                 }
+            }
         }
 
         applicationScope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 0b8f741..cef9a4e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard.ui.preview
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import android.app.WallpaperColors
 import android.content.BroadcastReceiver
 import android.content.Context
@@ -187,7 +188,7 @@
     private var themeStyle: Style? = null
 
     init {
-        coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job())
+        coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job() + createCoroutineTracingContext("KeyguardPreviewRenderer"))
         disposables += DisposableHandle { coroutineScope.cancel() }
         clockController.setFallbackWeatherData(WeatherData.getPlaceholderWeatherData())
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt
index aee34e1..1e42e19 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
 
 @SysUISingleton
 class DozingToGlanceableHubTransitionViewModel
@@ -35,10 +36,16 @@
         animationFlow
             .setup(
                 duration = TO_GLANCEABLE_HUB_DURATION,
-                edge = Edge.create(DOZING, Scenes.Communal)
+                edge = Edge.create(DOZING, Scenes.Communal),
             )
             .setupWithoutSceneContainer(edge = Edge.create(DOZING, GLANCEABLE_HUB))
 
     override val deviceEntryParentViewAlpha: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(1f)
+
+    /**
+     * Hide notifications when transitioning directly from dozing to hub, such as when pressing
+     * power button when dozing and docked.
+     */
+    val notificationAlpha: Flow<Float> = flowOf(0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 4b62eab..0d55709 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -179,14 +179,6 @@
         } else {
             button(KeyguardQuickAffordancePosition.BOTTOM_START)
         }
-        .stateIn(
-            scope = applicationScope,
-            started = SharingStarted.Eagerly,
-            initialValue =
-                KeyguardQuickAffordanceViewModel(
-                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()
-                ),
-        )
 
     /** An observable for the view-model of the "end button" quick affordance. */
     val endButton: Flow<KeyguardQuickAffordanceViewModel> =
@@ -200,14 +192,6 @@
         } else {
             button(KeyguardQuickAffordancePosition.BOTTOM_END)
         }
-        .stateIn(
-            scope = applicationScope,
-            started = SharingStarted.Eagerly,
-            initialValue =
-                KeyguardQuickAffordanceViewModel(
-                    slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId()
-                ),
-        )
 
     /**
      * Notifies that a slot with the given ID has been selected in the preview experience that is
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index c2b5d98..5559698 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -26,7 +26,7 @@
 import androidx.lifecycle.LifecycleRegistry
 import androidx.lifecycle.lifecycleScope
 import com.android.app.tracing.coroutines.createCoroutineTracingContext
-import com.android.app.tracing.coroutines.launch
+import com.android.app.tracing.coroutines.traceCoroutine
 import com.android.systemui.Flags.coroutineTracing
 import com.android.systemui.util.Assert
 import com.android.systemui.util.Compile
@@ -45,6 +45,7 @@
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
 
 /**
  * Runs the given [block] every time the [View] becomes attached (or immediately after calling this
@@ -137,7 +138,7 @@
 ): ViewLifecycleOwner {
     return ViewLifecycleOwner(view).apply {
         onCreate()
-        lifecycleScope.launch(nameForTrace, coroutineContext) { block(view) }
+        lifecycleScope.launch(coroutineContext) { traceCoroutine(nameForTrace) { block(view) } }
     }
 }
 
@@ -367,7 +368,8 @@
  * an extension function, and plumbing dagger-injected instances for static usage has little
  * benefit.
  */
-private val MAIN_DISPATCHER_SINGLETON = Dispatchers.Main + createCoroutineTracingContext()
+private val MAIN_DISPATCHER_SINGLETON =
+    Dispatchers.Main + createCoroutineTracingContext("RepeatWhenAttached")
 private const val DEFAULT_TRACE_NAME = "repeatWhenAttached"
 private const val CURRENT_CLASS_NAME = "com.android.systemui.lifecycle.RepeatWhenAttachedKt"
 private const val JAVA_ADAPTER_CLASS_NAME = "com.android.systemui.util.kotlin.JavaAdapterKt"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 8bec46a..70ca824 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -545,6 +545,7 @@
 
     /** Bind this player view based on the data given. */
     public void bindPlayer(@NonNull MediaData data, String key) {
+        SceneContainerFlag.assertInLegacyMode();
         if (mMediaViewHolder == null) {
             return;
         }
@@ -638,10 +639,7 @@
         // to something which might impact the measurement
         // State refresh interferes with the translation animation, only run it if it's not running.
         if (!mMetadataAnimationHandler.isRunning()) {
-            // Don't refresh in scene framework, because it will calculate with invalid layout sizes
-            if (!SceneContainerFlag.isEnabled()) {
-                mMediaViewController.refreshState();
-            }
+            mMediaViewController.refreshState();
         }
 
         if (shouldPlayTurbulenceNoise()) {
@@ -907,11 +905,6 @@
         // Capture width & height from views in foreground for artwork scaling in background
         int width = mMediaViewHolder.getAlbumView().getMeasuredWidth();
         int height = mMediaViewHolder.getAlbumView().getMeasuredHeight();
-        if (SceneContainerFlag.isEnabled() && (width <= 0 || height <= 0)) {
-            // TODO(b/312714128): ensure we have a valid size before setting background
-            width = mMediaViewController.getWidthInSceneContainerPx();
-            height = mMediaViewController.getHeightInSceneContainerPx();
-        }
 
         final int finalWidth = width;
         final int finalHeight = height;
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 6440205..544dbdd 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.mediaprojection.appselector
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import android.app.Activity
 import android.content.ComponentName
 import android.content.Context
@@ -133,7 +134,7 @@
         @MediaProjectionAppSelector
         @MediaProjectionAppSelectorScope
         fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope =
-            CoroutineScope(applicationScope.coroutineContext + SupervisorJob())
+            CoroutineScope(applicationScope.coroutineContext + SupervisorJob() + createCoroutineTracingContext("MediaProjectionAppSelectorScope"))
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index b3c697e..1216a88 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -551,6 +551,12 @@
     }
 
     @Override
+    public void appTransitionStarting(int displayId, long startTime, long duration,
+            boolean forced) {
+        appTransitionPending(false);
+    }
+
+    @Override
     public void appTransitionCancelled(int displayId) {
         appTransitionPending(false);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
index 02a607d..fc59a50 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -40,7 +40,7 @@
     private val currentTilesInteractor: CurrentTilesInteractor,
     private val preferencesInteractor: QSPreferencesInteractor,
     @PanelsLog private val logBuffer: LogBuffer,
-    @Application private val applicationScope: CoroutineScope
+    @Application private val applicationScope: CoroutineScope,
 ) {
 
     val largeTilesSpecs =
@@ -64,14 +64,15 @@
 
     fun isIconTile(spec: TileSpec): Boolean = !largeTilesSpecs.value.contains(spec)
 
-    fun resize(spec: TileSpec) {
+    fun resize(spec: TileSpec, toIcon: Boolean) {
         if (!isCurrent(spec)) {
             return
         }
 
-        if (largeTilesSpecs.value.contains(spec)) {
+        val isIcon = !largeTilesSpecs.value.contains(spec)
+        if (toIcon && !isIcon) {
             preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value - spec)
-        } else {
+        } else if (!toIcon && isIcon) {
             preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value + spec)
         }
     }
@@ -85,7 +86,7 @@
             LOG_BUFFER_LARGE_TILES_SPECS_CHANGE_TAG,
             LogLevel.DEBUG,
             { str1 = specs.toString() },
-            { "Large tiles change: $str1" }
+            { "Large tiles change: $str1" },
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
index a4f977b..770fd78 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
@@ -60,10 +60,37 @@
         return _tiles.filterIsInstance<TileGridCell>().map { it.tile.tileSpec }
     }
 
-    fun indexOf(tileSpec: TileSpec): Int {
+    private fun indexOf(tileSpec: TileSpec): Int {
         return _tiles.indexOfFirst { it is TileGridCell && it.tile.tileSpec == tileSpec }
     }
 
+    /**
+     * Whether the tile with this [TileSpec] is currently an icon in the [EditTileListState]
+     *
+     * @return true if the tile is an icon, false if it's large, null if the tile isn't in the list
+     */
+    fun isIcon(tileSpec: TileSpec): Boolean? {
+        val index = indexOf(tileSpec)
+        return if (index != -1) {
+            val cell = _tiles[index]
+            cell as TileGridCell
+            return cell.isIcon
+        } else {
+            null
+        }
+    }
+
+    /** Toggle the size of the tile corresponding to the [TileSpec] */
+    fun toggleSize(tileSpec: TileSpec) {
+        val fromIndex = indexOf(tileSpec)
+        if (fromIndex != -1) {
+            val cell = _tiles.removeAt(fromIndex)
+            cell as TileGridCell
+            _tiles.add(fromIndex, cell.copy(width = if (cell.isIcon) 2 else 1))
+            regenerateGrid(fromIndex)
+        }
+    }
+
     override fun isMoving(tileSpec: TileSpec): Boolean {
         return _draggedCell.value?.let { it.tile.tileSpec == tileSpec } ?: false
     }
@@ -71,8 +98,8 @@
     override fun onStarted(cell: SizedTile<EditTileViewModel>) {
         _draggedCell.value = cell
 
-        // Add visible spacers to the grid to indicate where the user can move a tile
-        regenerateGrid(includeSpacers = true)
+        // Add spacers to the grid to indicate where the user can move a tile
+        regenerateGrid()
     }
 
     override fun onMoved(target: Int, insertAfter: Boolean) {
@@ -86,7 +113,7 @@
         val insertionIndex = if (insertAfter) target + 1 else target
         if (fromIndex != -1) {
             val cell = _tiles.removeAt(fromIndex)
-            regenerateGrid(includeSpacers = true)
+            regenerateGrid()
             _tiles.add(insertionIndex.coerceIn(0, _tiles.size), cell)
         } else {
             // Add the tile with a temporary row which will get reassigned when
@@ -94,7 +121,7 @@
             _tiles.add(insertionIndex.coerceIn(0, _tiles.size), TileGridCell(draggedTile, 0))
         }
 
-        regenerateGrid(includeSpacers = true)
+        regenerateGrid()
     }
 
     override fun movedOutOfBounds() {
@@ -109,13 +136,28 @@
         _draggedCell.value = null
 
         // Remove the spacers
-        regenerateGrid(includeSpacers = false)
+        regenerateGrid()
     }
 
-    private fun regenerateGrid(includeSpacers: Boolean) {
-        _tiles.filterIsInstance<TileGridCell>().toGridCells(columns, includeSpacers).let {
+    /** Regenerate the list of [GridCell] with their new potential rows */
+    private fun regenerateGrid() {
+        _tiles.filterIsInstance<TileGridCell>().toGridCells(columns).let {
             _tiles.clear()
             _tiles.addAll(it)
         }
     }
+
+    /**
+     * Regenerate the list of [GridCell] with their new potential rows from [fromIndex], leaving
+     * cells before that untouched.
+     */
+    private fun regenerateGrid(fromIndex: Int) {
+        val fromRow = _tiles[fromIndex].row
+        val (pre, post) = _tiles.partition { it.row < fromRow }
+        post.filterIsInstance<TileGridCell>().toGridCells(columns, startingRow = fromRow).let {
+            _tiles.clear()
+            _tiles.addAll(pre)
+            _tiles.addAll(it)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index 0e76e18..30bafae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -132,15 +132,23 @@
 
 @Composable
 fun DefaultEditTileGrid(
-    currentListState: EditTileListState,
+    listState: EditTileListState,
     otherTiles: List<SizedTile<EditTileViewModel>>,
     columns: Int,
     modifier: Modifier,
     onRemoveTile: (TileSpec) -> Unit,
     onSetTiles: (List<TileSpec>) -> Unit,
-    onResize: (TileSpec) -> Unit,
+    onResize: (TileSpec, toIcon: Boolean) -> Unit,
 ) {
-    val selectionState = rememberSelectionState()
+    val currentListState by rememberUpdatedState(listState)
+    val selectionState =
+        rememberSelectionState(
+            onResize = { currentListState.toggleSize(it) },
+            onResizeEnd = { spec ->
+                // Commit the size currently in the list
+                currentListState.isIcon(spec)?.let { onResize(spec, it) }
+            },
+        )
 
     CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
         Column(
@@ -149,11 +157,11 @@
             modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState()),
         ) {
             AnimatedContent(
-                targetState = currentListState.dragInProgress,
+                targetState = listState.dragInProgress,
                 modifier = Modifier.wrapContentSize(),
                 label = "",
             ) { dragIsInProgress ->
-                EditGridHeader(Modifier.dragAndDropRemoveZone(currentListState, onRemoveTile)) {
+                EditGridHeader(Modifier.dragAndDropRemoveZone(listState, onRemoveTile)) {
                     if (dragIsInProgress) {
                         RemoveTileTarget()
                     } else {
@@ -162,11 +170,11 @@
                 }
             }
 
-            CurrentTilesGrid(currentListState, selectionState, columns, onResize, onSetTiles)
+            CurrentTilesGrid(listState, selectionState, columns, onResize, onSetTiles)
 
             // Hide available tiles when dragging
             AnimatedVisibility(
-                visible = !currentListState.dragInProgress,
+                visible = !listState.dragInProgress,
                 enter = fadeIn(),
                 exit = fadeOut(),
             ) {
@@ -177,7 +185,7 @@
                 ) {
                     EditGridHeader { Text(text = "Hold and drag to add tiles.") }
 
-                    AvailableTileGrid(otherTiles, selectionState, columns, currentListState)
+                    AvailableTileGrid(otherTiles, selectionState, columns, listState)
                 }
             }
 
@@ -186,7 +194,7 @@
                 modifier =
                     Modifier.fillMaxWidth()
                         .weight(1f)
-                        .dragAndDropRemoveZone(currentListState, onRemoveTile)
+                        .dragAndDropRemoveZone(listState, onRemoveTile)
             )
         }
     }
@@ -229,7 +237,7 @@
     listState: EditTileListState,
     selectionState: MutableSelectionState,
     columns: Int,
-    onResize: (TileSpec) -> Unit,
+    onResize: (TileSpec, toIcon: Boolean) -> Unit,
     onSetTiles: (List<TileSpec>) -> Unit,
 ) {
     val currentListState by rememberUpdatedState(listState)
@@ -242,19 +250,6 @@
         )
     val gridState = rememberLazyGridState()
     var gridContentOffset by remember { mutableStateOf(Offset(0f, 0f)) }
-    var droppedSpec by remember { mutableStateOf<TileSpec?>(null) }
-
-    // Select the tile that was dropped. A delay is introduced to avoid clipping issues on the
-    // selected border and resizing handle, as well as letting the selection animation play.
-    LaunchedEffect(droppedSpec) {
-        droppedSpec?.let {
-            delay(200)
-            selectionState.select(it)
-
-            // Reset droppedSpec in case a tile is dropped twice in a row
-            droppedSpec = null
-        }
-    }
 
     TileLazyGrid(
         state = gridState,
@@ -270,14 +265,17 @@
                 )
                 .dragAndDropTileList(gridState, { gridContentOffset }, listState) { spec ->
                     onSetTiles(currentListState.tileSpecs())
-                    droppedSpec = spec
+                    selectionState.select(spec, manual = false)
                 }
                 .onGloballyPositioned { coordinates ->
                     gridContentOffset = coordinates.positionInRoot()
                 }
                 .testTag(CURRENT_TILES_GRID_TEST_TAG),
     ) {
-        EditTiles(listState.tiles, listState, selectionState, onResize)
+        EditTiles(listState.tiles, listState, selectionState) { spec ->
+            // Toggle the current size of the tile
+            currentListState.isIcon(spec)?.let { onResize(spec, !it) }
+        }
     }
 }
 
@@ -348,11 +346,19 @@
     }
 }
 
+/**
+ * Adds a list of [GridCell] to the lazy grid
+ *
+ * @param cells the list of [GridCell]
+ * @param dragAndDropState the [DragAndDropState] for this grid
+ * @param selectionState the [MutableSelectionState] for this grid
+ * @param onToggleSize the callback when a tile's size is toggled
+ */
 fun LazyGridScope.EditTiles(
     cells: List<GridCell>,
     dragAndDropState: DragAndDropState,
     selectionState: MutableSelectionState,
-    onResize: (TileSpec) -> Unit,
+    onToggleSize: (spec: TileSpec) -> Unit,
 ) {
     items(
         count = cells.size,
@@ -378,7 +384,7 @@
                         index = index,
                         dragAndDropState = dragAndDropState,
                         selectionState = selectionState,
-                        onResize = onResize,
+                        onToggleSize = onToggleSize,
                     )
                 }
             is SpacerGridCell -> SpacerGridCell()
@@ -392,16 +398,28 @@
     index: Int,
     dragAndDropState: DragAndDropState,
     selectionState: MutableSelectionState,
-    onResize: (TileSpec) -> Unit,
+    onToggleSize: (spec: TileSpec) -> Unit,
 ) {
-    val selected = selectionState.isSelected(cell.tile.tileSpec)
     val stateDescription = stringResource(id = R.string.accessibility_qs_edit_position, index + 1)
+    var selected by remember { mutableStateOf(false) }
     val selectionAlpha by
         animateFloatAsState(
             targetValue = if (selected) 1f else 0f,
             label = "QSEditTileSelectionAlpha",
         )
 
+    LaunchedEffect(selectionState.selection?.tileSpec) {
+        selectionState.selection?.let {
+            // A delay is introduced on automatic selections such as dragged tiles or reflow caused
+            // by resizing. This avoids clipping issues on the border and resizing handle, as well
+            // as letting the selection animation play correctly.
+            if (!it.manual) {
+                delay(250)
+            }
+        }
+        selected = selectionState.selection?.tileSpec == cell.tile.tileSpec
+    }
+
     val modifier =
         Modifier.animateItem()
             .semantics(mergeDescendants = true) {
@@ -411,7 +429,7 @@
                     listOf(
                         // TODO(b/367748260): Add final accessibility actions
                         CustomAccessibilityAction("Toggle size") {
-                            onResize(cell.tile.tileSpec)
+                            onToggleSize(cell.tile.tileSpec)
                             true
                         }
                     )
@@ -438,11 +456,9 @@
 
     if (selected) {
         SelectedTile(
-            tileSpec = cell.tile.tileSpec,
             isIcon = cell.isIcon,
             selectionAlpha = { selectionAlpha },
             selectionState = selectionState,
-            onResize = onResize,
             modifier = modifier.zIndex(2f), // 2f to display this tile over neighbors when dragged
             content = content,
         )
@@ -458,11 +474,9 @@
 
 @Composable
 private fun SelectedTile(
-    tileSpec: TileSpec,
     isIcon: Boolean,
     selectionAlpha: () -> Float,
     selectionState: MutableSelectionState,
-    onResize: (TileSpec) -> Unit,
     modifier: Modifier = Modifier,
     content: @Composable () -> Unit,
 ) {
@@ -492,9 +506,7 @@
                     selectionState = selectionState,
                     transition = selectionAlpha,
                     tileWidths = { tileWidths },
-                ) {
-                    onResize(tileSpec)
-                }
+                )
             }
 
         Layout(contents = listOf(content, handle)) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index 8a96065..e6edba5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -101,7 +101,7 @@
         val (currentTiles, otherTiles) = sizedTiles.partition { it.tile.isCurrent }
         val currentListState = rememberEditListState(currentTiles, columns)
         DefaultEditTileGrid(
-            currentListState = currentListState,
+            listState = currentListState,
             otherTiles = otherTiles,
             columns = columns,
             modifier = modifier,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt
index 2ea32e6..441d962 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt
@@ -27,28 +27,39 @@
 
 /** Creates the state of the current selected tile that is remembered across compositions. */
 @Composable
-fun rememberSelectionState(): MutableSelectionState {
-    return remember { MutableSelectionState() }
+fun rememberSelectionState(
+    onResize: (TileSpec) -> Unit,
+    onResizeEnd: (TileSpec) -> Unit,
+): MutableSelectionState {
+    return remember { MutableSelectionState(onResize, onResizeEnd) }
 }
 
+/**
+ * Holds the selected [TileSpec] and whether the selection was manual, i.e. caused by a tap from the
+ * user.
+ */
+data class Selection(val tileSpec: TileSpec, val manual: Boolean)
+
 /** Holds the state of the current selection. */
-class MutableSelectionState {
-    private var _selectedTile = mutableStateOf<TileSpec?>(null)
+class MutableSelectionState(
+    val onResize: (TileSpec) -> Unit,
+    private val onResizeEnd: (TileSpec) -> Unit,
+) {
+    private var _selection = mutableStateOf<Selection?>(null)
     private var _resizingState = mutableStateOf<ResizingState?>(null)
 
+    /** The [Selection] if a tile is selected, null if not. */
+    val selection by _selection
+
     /** The [ResizingState] of the selected tile is currently being resized, null if not. */
     val resizingState by _resizingState
 
-    fun isSelected(tileSpec: TileSpec): Boolean {
-        return _selectedTile.value?.let { it == tileSpec } ?: false
-    }
-
-    fun select(tileSpec: TileSpec) {
-        _selectedTile.value = tileSpec
+    fun select(tileSpec: TileSpec, manual: Boolean) {
+        _selection.value = Selection(tileSpec, manual)
     }
 
     fun unSelect() {
-        _selectedTile.value = null
+        _selection.value = null
         onResizingDragEnd()
     }
 
@@ -56,14 +67,21 @@
         _resizingState.value?.onDrag(offset)
     }
 
-    fun onResizingDragStart(tileWidths: TileWidths, onResize: () -> Unit) {
-        if (_selectedTile.value == null) return
-
-        _resizingState.value = ResizingState(tileWidths, onResize)
+    fun onResizingDragStart(tileWidths: TileWidths) {
+        _selection.value?.let {
+            _resizingState.value = ResizingState(tileWidths) { onResize(it.tileSpec) }
+        }
     }
 
     fun onResizingDragEnd() {
         _resizingState.value = null
+        _selection.value?.let {
+            onResizeEnd(it.tileSpec)
+
+            // Mark the selection as automatic in case the tile ends up moving to a different
+            // row with its new size.
+            _selection.value = it.copy(manual = false)
+        }
     }
 }
 
@@ -76,10 +94,10 @@
     return pointerInput(Unit) {
         detectTapGestures(
             onTap = {
-                if (selectionState.isSelected(tileSpec)) {
+                if (selectionState.selection?.tileSpec == tileSpec) {
                     selectionState.unSelect()
                 } else {
-                    selectionState.select(tileSpec)
+                    selectionState.select(tileSpec, manual = true)
                 }
             }
         )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
index e3acf38..7c62e59 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
@@ -45,7 +45,6 @@
     selectionState: MutableSelectionState,
     transition: () -> Float,
     tileWidths: () -> TileWidths? = { null },
-    onResize: () -> Unit = {},
 ) {
     if (enabled) {
         // Manually creating the touch target around the resizing dot to ensure that the next tile
@@ -56,9 +55,7 @@
             Modifier.size(minTouchTargetSize).pointerInput(Unit) {
                 detectHorizontalDragGestures(
                     onHorizontalDrag = { _, offset -> selectionState.onResizingDrag(offset) },
-                    onDragStart = {
-                        tileWidths()?.let { selectionState.onResizingDragStart(it, onResize) }
-                    },
+                    onDragStart = { tileWidths()?.let { selectionState.onResizingDragStart(it) } },
                     onDragEnd = selectionState::onResizingDragEnd,
                     onDragCancel = selectionState::onResizingDragEnd,
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
index b16a707..b1841c4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
@@ -27,6 +27,7 @@
 sealed interface GridCell {
     val row: Int
     val span: GridItemSpan
+    val s: String
 }
 
 /**
@@ -39,6 +40,7 @@
     override val row: Int,
     override val width: Int,
     override val span: GridItemSpan = GridItemSpan(width),
+    override val s: String = "${tile.tileSpec.spec}-$row-$width",
 ) : GridCell, SizedTile<EditTileViewModel>, CategoryAndName by tile {
     val key: String = "${tile.tileSpec.spec}-$row"
 
@@ -53,22 +55,30 @@
 data class SpacerGridCell(
     override val row: Int,
     override val span: GridItemSpan = GridItemSpan(1),
+    override val s: String = "spacer",
 ) : GridCell
 
+/**
+ * Generates a list of [GridCell] from a list of [SizedTile]
+ *
+ * Builds rows based on the tiles' widths, and fill each hole with a [SpacerGridCell]
+ *
+ * @param startingRow The row index the grid is built from, used in cases where only end rows need
+ *   to be regenerated
+ */
 fun List<SizedTile<EditTileViewModel>>.toGridCells(
     columns: Int,
-    includeSpacers: Boolean = false,
+    startingRow: Int = 0,
 ): List<GridCell> {
     return splitInRowsSequence(this, columns)
         .flatMapIndexed { rowIndex, sizedTiles ->
-            val row: List<GridCell> = sizedTiles.map { TileGridCell(it, rowIndex) }
+            val correctedRowIndex = rowIndex + startingRow
+            val row: List<GridCell> = sizedTiles.map { TileGridCell(it, correctedRowIndex) }
 
-            if (includeSpacers) {
-                // Fill the incomplete rows with spacers
-                val numSpacers = columns - sizedTiles.sumOf { it.width }
-                row.toMutableList().apply { repeat(numSpacers) { add(SpacerGridCell(rowIndex)) } }
-            } else {
-                row
+            // Fill the incomplete rows with spacers
+            val numSpacers = columns - sizedTiles.sumOf { it.width }
+            row.toMutableList().apply {
+                repeat(numSpacers) { add(SpacerGridCell(correctedRowIndex)) }
             }
         }
         .toList()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
index b604e18..4e698ed 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
@@ -27,7 +27,7 @@
 
     fun isIconTile(spec: TileSpec): Boolean
 
-    fun resize(spec: TileSpec)
+    fun resize(spec: TileSpec, toIcon: Boolean)
 }
 
 @SysUISingleton
@@ -37,5 +37,5 @@
 
     override fun isIconTile(spec: TileSpec): Boolean = interactor.isIconTile(spec)
 
-    override fun resize(spec: TileSpec) = interactor.resize(spec)
+    override fun resize(spec: TileSpec, toIcon: Boolean) = interactor.resize(spec, toIcon)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt
index d0437a7..b8f4ab4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.base.viewmodel
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import com.android.systemui.dagger.qualifiers.Application
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -27,5 +28,5 @@
 constructor(@Application private val applicationScope: CoroutineScope) {
 
     fun create(): CoroutineScope =
-        CoroutineScope(applicationScope.coroutineContext + SupervisorJob())
+        CoroutineScope(applicationScope.coroutineContext + SupervisorJob() + createCoroutineTracingContext("QSTileScope"))
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt
index 246fe38..ae56c2a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.qs.tiles.dialog
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import android.util.Log
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.animation.DialogCuj
@@ -62,7 +63,7 @@
             }
             return
         } else {
-            coroutineScope = CoroutineScope(bgDispatcher)
+            coroutineScope = CoroutineScope(bgDispatcher + createCoroutineTracingContext("InternetDialogScope"))
             dialog =
                 dialogFactory
                     .create(aboveStatusBar, canConfigMobileData, canConfigWifi, coroutineScope)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt
index cca947f..ac75932 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.impl.location.domain.interactor
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import android.content.Intent
 import android.provider.Settings
 import com.android.systemui.dagger.qualifiers.Application
@@ -52,7 +53,7 @@
                     val wasEnabled: Boolean = input.data.isEnabled
                     if (keyguardController.isMethodSecure() && keyguardController.isShowing()) {
                         activityStarter.postQSRunnableDismissingKeyguard {
-                            CoroutineScope(applicationScope.coroutineContext).launch {
+                            CoroutineScope(applicationScope.coroutineContext + createCoroutineTracingContext("LocationTileScope")).launch {
                                 locationController.setLocationEnabled(!wasEnabled)
                             }
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt
index b25c61c..468e180 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.impl.saver.domain
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import android.content.Context
 import android.content.DialogInterface
 import android.content.SharedPreferences
@@ -44,7 +45,7 @@
             setTitle(R.string.data_saver_enable_title)
             setMessage(R.string.data_saver_description)
             setPositiveButton(R.string.data_saver_enable_button) { _: DialogInterface?, _ ->
-                CoroutineScope(backgroundContext).launch {
+                CoroutineScope(backgroundContext + createCoroutineTracingContext("DataSaverDialogScope")).launch {
                     dataSaverController.setDataSaverEnabled(true)
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index f76c5fd..0c05dbd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2220,6 +2220,7 @@
     @VisibleForTesting
     void onFlingEnd(boolean cancelled) {
         mIsFlinging = false;
+        mExpectingSynthesizedDown = false;
         // No overshoot when the animation ends
         setOverExpansionInternal(0, false /* isFromGesture */);
         setAnimator(null);
@@ -2352,7 +2353,6 @@
             return;
         }
         if (mExpectingSynthesizedDown) {
-            mExpectingSynthesizedDown = false;
             // Window never will receive touch events that typically trigger haptic on open.
             maybeVibrateOnOpening(false /* openingWithTouch */);
             fling(velocity > 1f ? 1000f * velocity : 0  /* expand */);
@@ -3994,6 +3994,9 @@
             }
             mExpandedFraction = Math.min(1f,
                     maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
+            if (mExpandedFraction > 0f && mExpectingSynthesizedDown) {
+                mExpectingSynthesizedDown = false;
+            }
             mShadeRepository.setLegacyShadeExpansion(mExpandedFraction);
             mQsController.setShadeExpansion(mExpandedHeight, mExpandedFraction);
             mExpansionDragDownAmountPx = h;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index bf88807..5473af3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -59,7 +59,6 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
-import com.android.systemui.scene.shared.model.Scenes;
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
 import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
 import com.android.systemui.statusbar.DragDownHelper;
@@ -265,20 +264,21 @@
     }
 
     private void bindBouncer(BouncerViewBinder bouncerViewBinder) {
+        mBouncerParentView = mView.findViewById(R.id.keyguard_bouncer_container);
+        bouncerViewBinder.bind(mBouncerParentView);
         if (ComposeBouncerFlags.INSTANCE.isOnlyComposeBouncerEnabled()) {
-            collectFlow(mView, mKeyguardTransitionInteractor.isFinishedIn(Scenes.Gone,
-                    KeyguardState.GONE), this::removeBouncerParentView);
             collectFlow(mView, mKeyguardTransitionInteractor.transition(
-                            new Edge.StateToState(KeyguardState.GONE, null)),
-                    this::handleGoneToAnyOtherStateTransition);
+                            new Edge.StateToState(KeyguardState.PRIMARY_BOUNCER, null)),
+                    this::onTransitionAwayFromBouncer);
+            collectFlow(mView, mKeyguardTransitionInteractor.transition(
+                            new Edge.StateToState(null, KeyguardState.PRIMARY_BOUNCER)),
+                    this::onTransitionToBouncer);
             collectFlow(mView, mPrimaryBouncerInteractor.isShowing(),
                     (showing) -> ViewKt.setVisible(mBouncerParentView, showing));
         }
-        mBouncerParentView = mView.findViewById(R.id.keyguard_bouncer_container);
-        bouncerViewBinder.bind(mBouncerParentView);
     }
 
-    private void handleGoneToAnyOtherStateTransition(TransitionStep transitionStep) {
+    private void onTransitionToBouncer(TransitionStep transitionStep) {
         if (transitionStep.getTransitionState() == TransitionState.STARTED) {
             if (mView.indexOfChild(mBouncerParentView) != -1) {
                 mView.removeView(mBouncerParentView);
@@ -287,8 +287,8 @@
         }
     }
 
-    private void removeBouncerParentView(boolean isFinishedInGoneState) {
-        if (isFinishedInGoneState) {
+    private void onTransitionAwayFromBouncer(TransitionStep transitionStep) {
+        if (transitionStep.getTransitionState() == TransitionState.FINISHED) {
             mView.removeView(mBouncerParentView);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 5eef8ea..769abaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -207,8 +207,8 @@
     protected boolean mPowerPluggedInWireless;
     protected boolean mPowerPluggedInDock;
     protected int mChargingSpeed;
+    protected boolean mPowerCharged;
 
-    private boolean mPowerCharged;
     /** Whether the battery defender is triggered. */
     private boolean mBatteryDefender;
     /** Whether the battery defender is triggered with the device plugged. */
@@ -1100,14 +1100,15 @@
             String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
             return mContext.getResources().getString(
                     R.string.keyguard_plugged_in_incompatible_charger, percentage);
-        } else if (mPowerCharged) {
-            return mContext.getResources().getString(R.string.keyguard_charged);
         }
 
         return computePowerChargingStringIndication();
     }
 
     protected String computePowerChargingStringIndication() {
+        if (mPowerCharged) {
+            return mContext.getResources().getString(R.string.keyguard_charged);
+        }
         final boolean hasChargingTime = mChargingTimeRemaining > 0;
         int chargingId;
         if (mPowerPluggedInWired) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 7f55512..8a6ec2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -50,8 +50,8 @@
 import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus;
 import com.android.systemui.keyguard.MigrateClocksToBlueprint;
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.data.model.SceneStack;
@@ -115,6 +115,7 @@
     private final UiEventLogger mUiEventLogger;
     private final Lazy<InteractionJankMonitor> mInteractionJankMonitorLazy;
     private final JavaAdapter mJavaAdapter;
+    private final Lazy<KeyguardInteractor> mKeyguardInteractorLazy;
     private final Lazy<KeyguardTransitionInteractor> mKeyguardTransitionInteractorLazy;
     private final Lazy<ShadeInteractor> mShadeInteractorLazy;
     private final Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy;
@@ -185,6 +186,7 @@
             UiEventLogger uiEventLogger,
             Lazy<InteractionJankMonitor> interactionJankMonitorLazy,
             JavaAdapter javaAdapter,
+            Lazy<KeyguardInteractor> keyguardInteractor,
             Lazy<KeyguardTransitionInteractor> keyguardTransitionInteractor,
             Lazy<ShadeInteractor> shadeInteractorLazy,
             Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy,
@@ -195,6 +197,7 @@
         mUiEventLogger = uiEventLogger;
         mInteractionJankMonitorLazy = interactionJankMonitorLazy;
         mJavaAdapter = javaAdapter;
+        mKeyguardInteractorLazy = keyguardInteractor;
         mKeyguardTransitionInteractorLazy = keyguardTransitionInteractor;
         mShadeInteractorLazy = shadeInteractorLazy;
         mDeviceUnlockedInteractorLazy = deviceUnlockedInteractorLazy;
@@ -233,8 +236,8 @@
                     this::onStatusBarStateChanged);
 
             mJavaAdapter.alwaysCollectFlow(
-                    mKeyguardTransitionInteractorLazy.get().transitionValue(KeyguardState.AOD),
-                    this::onAodKeyguardStateTransitionValueChanged);
+                    mKeyguardInteractorLazy.get().getDozeAmount(),
+                    this::setDozeAmountInternal);
         }
     }
 
@@ -404,6 +407,7 @@
 
     @Override
     public void setAndInstrumentDozeAmount(View view, float dozeAmount, boolean animated) {
+        SceneContainerFlag.assertInLegacyMode();
         if (mDarkAnimator != null && mDarkAnimator.isRunning()) {
             if (animated && mDozeAmountTarget == dozeAmount) {
                 return;
@@ -439,6 +443,7 @@
     }
 
     private void startDozeAnimation() {
+        SceneContainerFlag.assertInLegacyMode();
         if (mDozeAmount == 0f || mDozeAmount == 1f) {
             mDozeInterpolator = mIsDozing
                     ? Interpolators.FAST_OUT_SLOW_IN
@@ -457,6 +462,7 @@
 
     @VisibleForTesting
     protected ObjectAnimator createDarkAnimator() {
+        SceneContainerFlag.assertInLegacyMode();
         ObjectAnimator darkAnimator = ObjectAnimator.ofFloat(
                 this, SET_DARK_AMOUNT_PROPERTY, mDozeAmountTarget);
         darkAnimator.setInterpolator(Interpolators.LINEAR);
@@ -710,14 +716,6 @@
         updateStateAndNotifyListeners(newState);
     }
 
-    private void onAodKeyguardStateTransitionValueChanged(float value) {
-        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
-            return;
-        }
-
-        setDozeAmountInternal(value);
-    }
-
     private static final Map<SceneKey, Integer> sStatusBarStateByLockedSceneKey = Map.of(
             Scenes.Lockscreen, StatusBarState.KEYGUARD,
             Scenes.Bouncer, StatusBarState.KEYGUARD,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt
index 2bb4765..ce25cf5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt
@@ -17,8 +17,12 @@
 import android.app.StatusBarManager.DISABLE2_NONE
 import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
 import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
+import android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS
+import android.app.StatusBarManager.DISABLE_CLOCK
 import android.app.StatusBarManager.DISABLE_NONE
 import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS
+import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS
+import android.app.StatusBarManager.DISABLE_SYSTEM_INFO
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
@@ -27,12 +31,14 @@
  * Model for the disable flags that come from [IStatusBar].
  *
  * For clients of the disable flags: do *not* refer to the disable integers directly. Instead,
- * re-use or define a helper method that internally processes the flags. (We want to hide the
- * bitwise logic here so no one else has to worry about it.)
+ * re-use or define a helper method or property that internally processes the flags. (We want to
+ * hide the bitwise logic here so no one else has to worry about it.)
  */
 data class DisableFlagsModel(
     private val disable1: Int = DISABLE_NONE,
     private val disable2: Int = DISABLE2_NONE,
+    /** True if we should animate any view visibility changes and false otherwise. */
+    val animate: Boolean = false,
 ) {
     /** Returns true if notification alerts are allowed based on the flags. */
     fun areNotificationAlertsEnabled(): Boolean {
@@ -49,6 +55,13 @@
         return (disable2 and DISABLE2_QUICK_SETTINGS) == 0
     }
 
+    val isClockEnabled = (disable1 and DISABLE_CLOCK) == 0
+
+    val areNotificationIconsEnabled = (disable1 and DISABLE_NOTIFICATION_ICONS) == 0
+
+    val isSystemInfoEnabled =
+        (disable1 and DISABLE_SYSTEM_INFO) == 0 && (disable2 and DISABLE2_SYSTEM_ICONS) == 0
+
     /** Logs the change to the provided buffer. */
     fun logChange(buffer: LogBuffer, disableFlagsLogger: DisableFlagsLogger) {
         buffer.log(
@@ -60,9 +73,9 @@
             },
             {
                 disableFlagsLogger.getDisableFlagsString(
-                    new = DisableFlagsLogger.DisableState(int1, int2),
+                    new = DisableFlagsLogger.DisableState(int1, int2)
                 )
-            }
+            },
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt
index 13b74b4..9004e5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt
@@ -72,6 +72,7 @@
                                     // [QuickSettingsInteractor]-type class. However, that's out of
                                     // scope for the CentralSurfaces removal project.
                                     remoteInputQuickSettingsDisabler.adjustDisableFlags(state2),
+                                    animate,
                                 )
                             )
                         }
@@ -82,5 +83,5 @@
             .distinctUntilChanged()
             .onEach { it.logChange(logBuffer, disableFlagsLogger) }
             // Use Eagerly because we always need to know about disable flags
-            .stateIn(scope, SharingStarted.Eagerly, DisableFlagsModel())
+            .stateIn(scope, SharingStarted.Eagerly, DisableFlagsModel(animate = false))
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java
index 9b96931..6907eef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java
@@ -106,7 +106,8 @@
     @VisibleForTesting
     public final ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
             = new ArraySet<>();
-    private boolean mIsExpanded;
+    private boolean mIsShadeOrQsExpanded;
+    private boolean mIsQsExpanded;
     private int mStatusBarState;
     private AnimationStateHandler mAnimationStateHandler;
 
@@ -178,6 +179,10 @@
         });
         javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
                     this::onShadeOrQsExpanded);
+        if (SceneContainerFlag.isEnabled()) {
+            javaAdapter.alwaysCollectFlow(shadeInteractor.isQsExpanded(),
+                    this::onQsExpanded);
+        }
         if (NotificationThrottleHun.isEnabled()) {
             mVisualStabilityProvider.addPersistentReorderingBannedListener(
                     mOnReorderingBannedListener);
@@ -287,14 +292,19 @@
     }
 
     private void onShadeOrQsExpanded(Boolean isExpanded) {
-        if (isExpanded != mIsExpanded) {
-            mIsExpanded = isExpanded;
+        if (isExpanded != mIsShadeOrQsExpanded) {
+            mIsShadeOrQsExpanded = isExpanded;
             if (!SceneContainerFlag.isEnabled() && isExpanded) {
                 mHeadsUpAnimatingAway.setValue(false);
             }
         }
     }
 
+    private void onQsExpanded(Boolean isQsExpanded) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        if (isQsExpanded != mIsQsExpanded) mIsQsExpanded = isQsExpanded;
+    }
+
     /**
      * Set that we are exiting the headsUp pinned mode, but some notifications might still be
      * animating out. This is used to keep the touchable regions in a reasonable state.
@@ -490,7 +500,10 @@
 
     @Override
     protected boolean shouldHeadsUpBecomePinned(NotificationEntry entry) {
-        boolean pin = mStatusBarState == StatusBarState.SHADE && !mIsExpanded;
+        boolean pin = mStatusBarState == StatusBarState.SHADE && !mIsShadeOrQsExpanded;
+        if (SceneContainerFlag.isEnabled()) {
+            pin |= mIsQsExpanded;
+        }
         if (mBypassController.getBypassEnabled()) {
             pin |= mStatusBarState == StatusBarState.KEYGUARD;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 560028c..7b6a2cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -444,11 +444,9 @@
                 if (onFinishedRunnable != null) {
                     onFinishedRunnable.run();
                 }
-                if (mRunWithoutInterruptions) {
-                    enableAppearDrawing(false);
-                }
 
                 // We need to reset the View state, even if the animation was cancelled
+                enableAppearDrawing(false);
                 onAppearAnimationFinished(isAppearing);
 
                 if (mRunWithoutInterruptions) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 48796d8..b108388 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -634,8 +634,10 @@
     }
 
     private View inflateDivider() {
-        return LayoutInflater.from(mContext).inflate(
+        View divider = LayoutInflater.from(mContext).inflate(
                 R.layout.notification_children_divider, this, false);
+        divider.setAlpha(0f);
+        return divider;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index e7c67f9..3c6962a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1260,6 +1260,7 @@
     @Override
     public void setHeadsUpBottom(float headsUpBottom) {
         mAmbientState.setHeadsUpBottom(headsUpBottom);
+        mStateAnimator.setHeadsUpAppearHeightBottom(Math.round(headsUpBottom));
     }
 
     @Override
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 e34eb61..8ca26be 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
@@ -45,6 +45,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.DozingToGlanceableHubTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
@@ -112,6 +113,7 @@
     private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
     private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
     private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
+    dozingToGlanceableHubTransitionViewModel: DozingToGlanceableHubTransitionViewModel,
     private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
     private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
     private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
@@ -506,6 +508,7 @@
                 merge(
                         lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
                         glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
+                        dozingToGlanceableHubTransitionViewModel.notificationAlpha,
                     )
                     // Manually emit on start because [notificationAlpha] only starts emitting
                     // when transitions start.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 6584556..a8b4728 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -56,7 +56,8 @@
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips;
-import com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
+import com.android.systemui.statusbar.core.StatusBarSimpleFragment;
+import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
 import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
@@ -366,8 +367,10 @@
         mPrimaryOngoingActivityChip = mStatusBar.findViewById(R.id.ongoing_activity_chip_primary);
         mSecondaryOngoingActivityChip =
                 mStatusBar.findViewById(R.id.ongoing_activity_chip_secondary);
-        showEndSideContent(false);
-        showClock(false);
+        if (!StatusBarSimpleFragment.isEnabled()) {
+            showEndSideContent(false);
+            showClock(false);
+        }
         initOperatorName();
         initNotificationIconArea();
         mSystemEventAnimator = getSystemEventAnimator();
@@ -455,7 +458,9 @@
         super.onPause();
         mCommandQueue.removeCallback(this);
         mStatusBarStateController.removeCallback(this);
-        mOngoingCallController.removeCallback(mOngoingCallListener);
+        if (!StatusBarSimpleFragment.isEnabled()) {
+            mOngoingCallController.removeCallback(mOngoingCallListener);
+        }
         mAnimationScheduler.removeCallback(this);
         mSecureSettings.unregisterContentObserverSync(mVolumeSettingObserver);
     }
@@ -490,7 +495,9 @@
         mNotificationIconAreaInner = notificationIcons;
         mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons);
 
-        updateNotificationIconAreaAndOngoingActivityChip(/* animate= */ false);
+        if (!StatusBarSimpleFragment.isEnabled()) {
+            updateNotificationIconAreaAndOngoingActivityChip(/* animate= */ false);
+        }
         Trace.endSection();
     }
 
@@ -509,11 +516,17 @@
             new StatusBarVisibilityChangeListener() {
                 @Override
                 public void onStatusBarVisibilityMaybeChanged() {
+                    if (StatusBarSimpleFragment.isEnabled()) {
+                        return;
+                    }
                     updateStatusBarVisibilities(/* animate= */ true);
                 }
 
                 @Override
                 public void onTransitionFromLockscreenToDreamStarted() {
+                    if (StatusBarSimpleFragment.isEnabled()) {
+                        return;
+                    }
                     mTransitionFromLockscreenToDreamStarted = true;
                 }
 
@@ -522,6 +535,9 @@
                         boolean hasPrimaryOngoingActivity,
                         boolean hasSecondaryOngoingActivity,
                         boolean shouldAnimate) {
+                    if (StatusBarSimpleFragment.isEnabled()) {
+                        return;
+                    }
                     mHasPrimaryOngoingActivity = hasPrimaryOngoingActivity;
                     mHasSecondaryOngoingActivity = hasSecondaryOngoingActivity;
                     updateStatusBarVisibilities(shouldAnimate);
@@ -530,6 +546,9 @@
                 @Override
                 public void onIsHomeStatusBarAllowedBySceneChanged(
                         boolean isHomeStatusBarAllowedByScene) {
+                    if (StatusBarSimpleFragment.isEnabled()) {
+                        return;
+                    }
                     mHomeStatusBarAllowedByScene = isHomeStatusBarAllowedByScene;
                     updateStatusBarVisibilities(/* animate= */ true);
                 }
@@ -537,17 +556,22 @@
 
     @Override
     public void disable(int displayId, int state1, int state2, boolean animate) {
+        if (StatusBarSimpleFragment.isEnabled()) {
+            return;
+        }
         if (displayId != getContext().getDisplayId()) {
             return;
         }
         mCollapsedStatusBarFragmentLogger
-                .logDisableFlagChange(new DisableState(state1, state2));
+                .logDisableFlagChange(new DisableFlagsLogger.DisableState(state1, state2));
         mLastSystemVisibility =
                 StatusBarVisibilityModel.createModelFromFlags(state1, state2);
         updateStatusBarVisibilities(animate);
     }
 
     private void updateStatusBarVisibilities(boolean animate) {
+        StatusBarSimpleFragment.assertInLegacyMode();
+
         StatusBarVisibilityModel previousModel = mLastModifiedVisibility;
         StatusBarVisibilityModel newModel = calculateInternalModel(mLastSystemVisibility);
         mCollapsedStatusBarFragmentLogger.logVisibilityModel(newModel);
@@ -587,6 +611,8 @@
 
     private StatusBarVisibilityModel calculateInternalModel(
             StatusBarVisibilityModel externalModel) {
+        StatusBarSimpleFragment.assertInLegacyMode();
+
         // TODO(b/328393714) use HeadsUpNotificationInteractor.showHeadsUpStatusBar instead.
         boolean headsUpVisible =
                 mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible();
@@ -639,6 +665,8 @@
      * mLastModifiedVisibility.
      */
     private void updateNotificationIconAreaAndOngoingActivityChip(boolean animate) {
+        StatusBarSimpleFragment.assertInLegacyMode();
+
         StatusBarVisibilityModel visibilityModel = mLastModifiedVisibility;
         boolean disableNotifications = !visibilityModel.getShowNotificationIcons();
         boolean hasOngoingActivity = visibilityModel.getShowPrimaryOngoingActivityChip();
@@ -674,6 +702,8 @@
     }
 
     private boolean shouldHideStatusBar() {
+        StatusBarSimpleFragment.assertInLegacyMode();
+
         if (!mShadeExpansionStateManager.isClosed()
                 && mPanelExpansionInteractor.shouldHideStatusBarIconsWhenExpanded()) {
             return true;
@@ -728,6 +758,7 @@
     }
 
     private void hideEndSideContent(boolean animate) {
+        StatusBarSimpleFragment.assertInLegacyMode();
         if (!animate || !mAnimationsEnabled) {
             mEndSideAlphaController.setAlpha(/*alpha*/ 0f, SOURCE_OTHER);
         } else {
@@ -737,6 +768,7 @@
     }
 
     private void showEndSideContent(boolean animate) {
+        StatusBarSimpleFragment.assertInLegacyMode();
         if (!animate || !mAnimationsEnabled) {
             mEndSideAlphaController.setAlpha(1f, SOURCE_OTHER);
             return;
@@ -753,15 +785,18 @@
     }
 
     private void hideClock(boolean animate) {
+        StatusBarSimpleFragment.assertInLegacyMode();
         animateHiddenState(mClockView, clockHiddenMode(), animate);
     }
 
     private void showClock(boolean animate) {
+        StatusBarSimpleFragment.assertInLegacyMode();
         animateShow(mClockView, animate);
     }
 
     /** Hides the primary ongoing activity chip. */
     private void hidePrimaryOngoingActivityChip(boolean animate) {
+        StatusBarSimpleFragment.assertInLegacyMode();
         animateHiddenState(mPrimaryOngoingActivityChip, View.GONE, animate);
     }
 
@@ -773,15 +808,18 @@
      * activities. See b/332662551.
      */
     private void showPrimaryOngoingActivityChip(boolean animate) {
+        StatusBarSimpleFragment.assertInLegacyMode();
         animateShow(mPrimaryOngoingActivityChip, animate);
     }
 
     private void hideSecondaryOngoingActivityChip(boolean animate) {
+        StatusBarSimpleFragment.assertInLegacyMode();
         animateHiddenState(mSecondaryOngoingActivityChip, View.GONE, animate);
     }
 
     private void showSecondaryOngoingActivityChip(boolean animate) {
         StatusBarRonChips.assertInNewMode();
+        StatusBarSimpleFragment.assertInLegacyMode();
         animateShow(mSecondaryOngoingActivityChip, animate);
     }
 
@@ -790,6 +828,7 @@
      * don't set the clock GONE otherwise it'll mess up the animation.
      */
     private int clockHiddenMode() {
+        StatusBarSimpleFragment.assertInLegacyMode();
         if (!mShadeExpansionStateManager.isClosed() && !mKeyguardStateController.isShowing()
                 && !mStatusBarStateController.isDozing()) {
             return View.INVISIBLE;
@@ -798,20 +837,24 @@
     }
 
     public void hideNotificationIconArea(boolean animate) {
+        StatusBarSimpleFragment.assertInLegacyMode();
         animateHide(mNotificationIconAreaInner, animate);
     }
 
     public void showNotificationIconArea(boolean animate) {
+        StatusBarSimpleFragment.assertInLegacyMode();
         animateShow(mNotificationIconAreaInner, animate);
     }
 
     public void hideOperatorName(boolean animate) {
+        StatusBarSimpleFragment.assertInLegacyMode();
         if (mOperatorNameViewController != null) {
             animateHide(mOperatorNameViewController.getView(), animate);
         }
     }
 
     public void showOperatorName(boolean animate) {
+        StatusBarSimpleFragment.assertInLegacyMode();
         if (mOperatorNameViewController != null) {
             animateShow(mOperatorNameViewController.getView(), animate);
         }
@@ -821,6 +864,7 @@
      * Animate a view to INVISIBLE or GONE
      */
     private void animateHiddenState(final View v, int state, boolean animate) {
+        StatusBarSimpleFragment.assertInLegacyMode();
         v.animate().cancel();
         if (!animate || !mAnimationsEnabled) {
             v.setAlpha(0f);
@@ -840,6 +884,7 @@
      * Hides a view.
      */
     private void animateHide(final View v, boolean animate) {
+        StatusBarSimpleFragment.assertInLegacyMode();
         animateHiddenState(v, View.INVISIBLE, animate);
     }
 
@@ -847,6 +892,7 @@
      * Shows a view, and synchronizes the animation with Keyguard exit animations, if applicable.
      */
     private void animateShow(View v, boolean animate) {
+        StatusBarSimpleFragment.assertInLegacyMode();
         v.animate().cancel();
         v.setVisibility(View.VISIBLE);
         if (!animate || !mAnimationsEnabled) {
@@ -883,13 +929,17 @@
             mOperatorNameViewController.init();
             // This view should not be visible on lock-screen
             if (mKeyguardStateController.isShowing()) {
-                hideOperatorName(false);
+                if (!StatusBarSimpleFragment.isEnabled()) {
+                    hideOperatorName(false);
+                }
             }
         }
     }
 
     private void initOngoingCallChip() {
-        mOngoingCallController.addCallback(mOngoingCallListener);
+        if (!StatusBarSimpleFragment.isEnabled()) {
+            mOngoingCallController.addCallback(mOngoingCallListener);
+        }
         // TODO(b/364653005): Do we also need to set the secondary activity chip?
         mOngoingCallController.setChipView(mPrimaryOngoingActivityChip);
     }
@@ -899,6 +949,9 @@
 
     @Override
     public void onDozingChanged(boolean isDozing) {
+        if (StatusBarSimpleFragment.isEnabled()) {
+            return;
+        }
         updateStatusBarVisibilities(/* animate= */ false);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt
index c8836e4..eaf15a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt
@@ -20,6 +20,7 @@
 import androidx.core.animation.Interpolator
 import androidx.core.animation.ValueAnimator
 import com.android.app.animation.InterpolatorsAndroidX
+import com.android.systemui.statusbar.core.StatusBarSimpleFragment
 
 /**
  * A controller that keeps track of multiple sources applying alpha value changes to a view. It will
@@ -48,7 +49,7 @@
         sourceId: Int,
         duration: Long,
         interpolator: Interpolator = InterpolatorsAndroidX.ALPHA_IN,
-        startDelay: Long = 0
+        startDelay: Long = 0,
     ) {
         animators[sourceId]?.cancel()
         val animator = ValueAnimator.ofFloat(getMinAlpha(), alpha)
@@ -74,8 +75,10 @@
 
     private fun applyAlphaToView() {
         val minAlpha = getMinAlpha()
-        view.visibility = if (minAlpha != 0f) View.VISIBLE else View.INVISIBLE
-        view.alpha = minAlpha
+        if (!StatusBarSimpleFragment.isEnabled) {
+            view.visibility = if (minAlpha != 0f) View.VISIBLE else View.INVISIBLE
+            view.alpha = minAlpha
+        }
     }
 
     private fun getMinAlpha() = alphas.minOfOrNull { it.value } ?: initialAlpha
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 68163b2..4ef328c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -149,7 +149,7 @@
     override val isForceHidden: Flow<Boolean>,
     connectionRepository: MobileConnectionRepository,
     private val context: Context,
-    val carrierIdOverrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
+    val carrierIdOverrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl(),
 ) : MobileIconInteractor {
     override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer
 
@@ -182,7 +182,7 @@
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
-                connectionRepository.networkName.value
+                connectionRepository.networkName.value,
             )
 
     override val carrierName =
@@ -198,7 +198,7 @@
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
-                connectionRepository.carrierName.value.name
+                connectionRepository.carrierName.value.name,
             )
 
     /** What the mobile icon would be before carrierId overrides */
@@ -219,10 +219,7 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
 
     override val networkTypeIconGroup =
-        combine(
-                defaultNetworkType,
-                carrierIdIconOverrideExists,
-            ) { networkType, overrideExists ->
+        combine(defaultNetworkType, carrierIdIconOverrideExists) { networkType, overrideExists ->
                 // DefaultIcon comes out of the icongroup lookup, we check for overrides here
                 if (overrideExists) {
                     val iconOverride =
@@ -316,30 +313,30 @@
 
     /** Whether or not to show the error state of [SignalDrawable] */
     private val showExclamationMark: StateFlow<Boolean> =
-        combine(
-                defaultSubscriptionHasDataEnabled,
+        combine(defaultSubscriptionHasDataEnabled, isDefaultConnectionFailed, isInService) {
+                isDefaultDataEnabled,
                 isDefaultConnectionFailed,
-                isInService,
-            ) { isDefaultDataEnabled, isDefaultConnectionFailed, isInService ->
+                isInService ->
                 !isDefaultDataEnabled || isDefaultConnectionFailed || !isInService
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), true)
 
     private val cellularShownLevel: StateFlow<Int> =
-        combine(
+        combine(level, isInService, connectionRepository.inflateSignalStrength) {
                 level,
                 isInService,
-                connectionRepository.inflateSignalStrength,
-            ) { level, isInService, inflate ->
+                inflate ->
                 if (isInService) {
                     if (inflate) level + 1 else level
                 } else 0
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
 
-    // Satellite level is unaffected by the isInService or inflateSignalStrength properties
+    // Satellite level is unaffected by the inflateSignalStrength property
     // See b/346904529 for details
-    private val satelliteShownLevel: StateFlow<Int> = level
+    private val satelliteShownLevel: StateFlow<Int> =
+        combine(level, isInService) { level, isInService -> if (isInService) level else 0 }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
 
     private val cellularIcon: Flow<SignalIconModel.Cellular> =
         combine(
@@ -362,7 +359,7 @@
                 level = it,
                 icon =
                     SatelliteIconModel.fromSignalStrength(it)
-                        ?: SatelliteIconModel.fromSignalStrength(0)!!
+                        ?: SatelliteIconModel.fromSignalStrength(0)!!,
             )
         }
 
@@ -383,11 +380,7 @@
                 }
             }
             .distinctUntilChanged()
-            .logDiffsForTable(
-                tableLogBuffer,
-                columnPrefix = "icon",
-                initialValue = initial,
-            )
+            .logDiffsForTable(tableLogBuffer, columnPrefix = "icon", initialValue = initial)
             .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index deae576..bad6f80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -29,6 +30,7 @@
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.cancel
@@ -114,7 +116,7 @@
 
     private fun createViewModel(subId: Int): Pair<MobileIconViewModel, CoroutineScope> {
         // Create a child scope so we can cancel it
-        val vmScope = scope.createChildScope()
+        val vmScope = scope.createChildScope(createCoroutineTracingContext("MobileIconViewModel"))
         val vm =
             MobileIconViewModel(
                 subId,
@@ -128,8 +130,8 @@
         return Pair(vm, vmScope)
     }
 
-    private fun CoroutineScope.createChildScope() =
-        CoroutineScope(coroutineContext + Job(coroutineContext[Job]))
+    private fun CoroutineScope.createChildScope(extraContext: CoroutineContext) =
+        CoroutineScope(coroutineContext + Job(coroutineContext[Job]) + extraContext)
 
     private fun invalidateCaches(subIds: List<Int>) {
         reuseCache.keys
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt
new file mode 100644
index 0000000..9164da7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
+import com.android.systemui.statusbar.pipeline.shared.domain.model.StatusBarDisableFlagsVisibilityModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/**
+ * Interactor for the home screen status bar (aka
+ * [com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment]).
+ */
+@SysUISingleton
+class CollapsedStatusBarInteractor
+@Inject
+constructor(disableFlagsRepository: DisableFlagsRepository) {
+    /**
+     * The visibilities of various status bar child views, based only on the information we received
+     * from disable flags.
+     */
+    val visibilityViaDisableFlags: Flow<StatusBarDisableFlagsVisibilityModel> =
+        disableFlagsRepository.disableFlags.map {
+            StatusBarDisableFlagsVisibilityModel(
+                isClockAllowed = it.isClockEnabled,
+                areNotificationIconsAllowed = it.areNotificationIconsEnabled,
+                isSystemInfoAllowed = it.isSystemInfoEnabled,
+                animate = it.animate,
+            )
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/model/StatusBarDisableFlagsVisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/model/StatusBarDisableFlagsVisibilityModel.kt
new file mode 100644
index 0000000..69e9746
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/model/StatusBarDisableFlagsVisibilityModel.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.domain.model
+
+/**
+ * Represents the visibilities of various status bar child views, based only on the information we
+ * received from disable flags.
+ */
+data class StatusBarDisableFlagsVisibilityModel(
+    /** True if the clock is allowed to be shown. */
+    val isClockAllowed: Boolean,
+    /** True if the notification icons are allowed to be shown. */
+    val areNotificationIconsAllowed: Boolean,
+    /** True if the system information (wifi, mobile, etc.) is allowed to be shown. */
+    val isSystemInfoAllowed: Boolean,
+    /** True if we should animate any view visibility changes and false otherwise. */
+    val animate: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
index 49eabba..eea4c21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
@@ -21,6 +21,7 @@
 import android.view.View
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
 import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -28,7 +29,9 @@
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.core.StatusBarSimpleFragment
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
+import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel
 import javax.inject.Inject
 import kotlinx.coroutines.launch
@@ -86,19 +89,30 @@
                     launch {
                         viewModel.primaryOngoingActivityChip.collect { primaryChipModel ->
                             OngoingActivityChipBinder.bind(primaryChipModel, primaryChipView)
-                            when (primaryChipModel) {
-                                is OngoingActivityChipModel.Shown ->
-                                    listener.onOngoingActivityStatusChanged(
-                                        hasPrimaryOngoingActivity = true,
-                                        hasSecondaryOngoingActivity = false,
-                                        shouldAnimate = true,
-                                    )
-                                is OngoingActivityChipModel.Hidden ->
-                                    listener.onOngoingActivityStatusChanged(
-                                        hasPrimaryOngoingActivity = false,
-                                        hasSecondaryOngoingActivity = false,
-                                        shouldAnimate = primaryChipModel.shouldAnimate,
-                                    )
+                            if (StatusBarSimpleFragment.isEnabled) {
+                                when (primaryChipModel) {
+                                    is OngoingActivityChipModel.Shown ->
+                                        primaryChipView.show(shouldAnimateChange = true)
+                                    is OngoingActivityChipModel.Hidden ->
+                                        primaryChipView.hide(
+                                            shouldAnimateChange = primaryChipModel.shouldAnimate
+                                        )
+                                }
+                            } else {
+                                when (primaryChipModel) {
+                                    is OngoingActivityChipModel.Shown ->
+                                        listener.onOngoingActivityStatusChanged(
+                                            hasPrimaryOngoingActivity = true,
+                                            hasSecondaryOngoingActivity = false,
+                                            shouldAnimate = true,
+                                        )
+                                    is OngoingActivityChipModel.Hidden ->
+                                        listener.onOngoingActivityStatusChanged(
+                                            hasPrimaryOngoingActivity = false,
+                                            hasSecondaryOngoingActivity = false,
+                                            shouldAnimate = primaryChipModel.shouldAnimate,
+                                        )
+                                }
                             }
                         }
                     }
@@ -115,14 +129,22 @@
                             // TODO(b/364653005): Don't show the secondary chip if there isn't
                             // enough space for it.
                             OngoingActivityChipBinder.bind(chips.secondary, secondaryChipView)
-                            listener.onOngoingActivityStatusChanged(
-                                hasPrimaryOngoingActivity =
-                                    chips.primary is OngoingActivityChipModel.Shown,
-                                hasSecondaryOngoingActivity =
-                                    chips.secondary is OngoingActivityChipModel.Shown,
-                                // TODO(b/364653005): Figure out the animation story here.
-                                shouldAnimate = true,
-                            )
+
+                            if (StatusBarSimpleFragment.isEnabled) {
+                                primaryChipView.adjustVisibility(chips.primary.toVisibilityModel())
+                                secondaryChipView.adjustVisibility(
+                                    chips.secondary.toVisibilityModel()
+                                )
+                            } else {
+                                listener.onOngoingActivityStatusChanged(
+                                    hasPrimaryOngoingActivity =
+                                        chips.primary is OngoingActivityChipModel.Shown,
+                                    hasSecondaryOngoingActivity =
+                                        chips.secondary is OngoingActivityChipModel.Shown,
+                                    // TODO(b/364653005): Figure out the animation story here.
+                                    shouldAnimate = true,
+                                )
+                            }
                         }
                     }
                 }
@@ -134,10 +156,42 @@
                         }
                     }
                 }
+
+                if (StatusBarSimpleFragment.isEnabled) {
+                    val clockView = view.requireViewById<View>(R.id.clock)
+                    launch { viewModel.isClockVisible.collect { clockView.adjustVisibility(it) } }
+
+                    val notificationIconsArea = view.requireViewById<View>(R.id.notificationIcons)
+                    launch {
+                        viewModel.isNotificationIconContainerVisible.collect {
+                            notificationIconsArea.adjustVisibility(it)
+                        }
+                    }
+
+                    val systemInfoView =
+                        view.requireViewById<View>(R.id.status_bar_end_side_content)
+                    // TODO(b/364360986): Also handle operator name view.
+                    launch {
+                        viewModel.isSystemInfoVisible.collect {
+                            systemInfoView.adjustVisibility(it)
+                            // TODO(b/364360986): The system info view has a custom alpha controller
+                            // in CollapsedStatusBarFragment.
+                        }
+                    }
+                }
             }
         }
     }
 
+    private fun OngoingActivityChipModel.toVisibilityModel():
+        CollapsedStatusBarViewModel.VisibilityModel {
+        return CollapsedStatusBarViewModel.VisibilityModel(
+            visibility = if (this is OngoingActivityChipModel.Shown) View.VISIBLE else View.GONE,
+            // TODO(b/364653005): Figure out the animation story here.
+            shouldAnimateChange = true,
+        )
+    }
+
     private fun animateLightsOutView(view: View, visible: Boolean) {
         view.animate().cancel()
 
@@ -167,6 +221,54 @@
             )
             .start()
     }
+
+    private fun View.adjustVisibility(model: CollapsedStatusBarViewModel.VisibilityModel) {
+        if (model.visibility == View.VISIBLE) {
+            this.show(model.shouldAnimateChange)
+        } else {
+            this.hide(model.visibility, model.shouldAnimateChange)
+        }
+    }
+
+    // See CollapsedStatusBarFragment#hide.
+    private fun View.hide(state: Int = View.INVISIBLE, shouldAnimateChange: Boolean) {
+        val v = this
+        v.animate().cancel()
+        if (!shouldAnimateChange) {
+            v.alpha = 0f
+            v.visibility = state
+            return
+        }
+
+        v.animate()
+            .alpha(0f)
+            .setDuration(CollapsedStatusBarFragment.FADE_OUT_DURATION.toLong())
+            .setStartDelay(0)
+            .setInterpolator(Interpolators.ALPHA_OUT)
+            .withEndAction { v.visibility = state }
+    }
+
+    // See CollapsedStatusBarFragment#show.
+    private fun View.show(shouldAnimateChange: Boolean) {
+        val v = this
+        v.animate().cancel()
+        v.visibility = View.VISIBLE
+        if (!shouldAnimateChange) {
+            v.alpha = 1f
+            return
+        }
+        v.animate()
+            .alpha(1f)
+            .setDuration(CollapsedStatusBarFragment.FADE_IN_DURATION.toLong())
+            .setInterpolator(Interpolators.ALPHA_IN)
+            .setStartDelay(CollapsedStatusBarFragment.FADE_IN_DELAY.toLong())
+            // We need to clean up any pending end action from animateHide if we call both hide and
+            // show in the same frame before the animation actually gets started.
+            // cancel() doesn't really remove the end action.
+            .withEndAction(null)
+
+        // TODO(b/364360986): Synchronize the motion with the Keyguard fading if necessary.
+    }
 }
 
 /** Listener for various events that may affect the status bar's visibility. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
index 9cce2b8..366ea35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
@@ -16,23 +16,29 @@
 
 package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
 
+import android.view.View
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+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.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor
+import com.android.systemui.statusbar.pipeline.shared.domain.interactor.CollapsedStatusBarInteractor
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel.VisibilityModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -80,9 +86,16 @@
     /**
      * True if the current scene can show the home status bar (aka this status bar), and false if
      * the current scene should never show the home status bar.
+     *
+     * TODO(b/364360986): Once the is<SomeChildView>Visible flows are fully enabled, we shouldn't
+     *   need this flow anymore.
      */
     val isHomeStatusBarAllowedByScene: StateFlow<Boolean>
 
+    val isClockVisible: Flow<VisibilityModel>
+    val isNotificationIconContainerVisible: Flow<VisibilityModel>
+    val isSystemInfoVisible: Flow<VisibilityModel>
+
     /**
      * Apps can request a low profile mode [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE] where
      * status bar and navigation icons dim. In this mode, a notification dot appears where the
@@ -93,17 +106,26 @@
      * [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE].
      */
     fun areNotificationsLightsOut(displayId: Int): Flow<Boolean>
+
+    /** Models the current visibility for a specific child view of status bar. */
+    data class VisibilityModel(
+        @View.Visibility val visibility: Int,
+        /** True if a visibility change should be animated. */
+        val shouldAnimateChange: Boolean,
+    )
 }
 
 @SysUISingleton
 class CollapsedStatusBarViewModelImpl
 @Inject
 constructor(
+    collapsedStatusBarInteractor: CollapsedStatusBarInteractor,
     private val lightsOutInteractor: LightsOutInteractor,
     private val notificationsInteractor: ActiveNotificationsInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     sceneInteractor: SceneInteractor,
     sceneContainerOcclusionInteractor: SceneContainerOcclusionInteractor,
+    shadeInteractor: ShadeInteractor,
     ongoingActivityChipsViewModel: OngoingActivityChipsViewModel,
     @Application coroutineScope: CoroutineScope,
 ) : CollapsedStatusBarViewModel {
@@ -148,4 +170,59 @@
                 }
                 .distinctUntilChanged()
         }
+
+    /**
+     * True if the current SysUI state can show the home status bar (aka this status bar), and false
+     * if we shouldn't be showing any part of the home status bar.
+     */
+    private val isHomeScreenStatusBarAllowedLegacy: Flow<Boolean> =
+        combine(
+            keyguardTransitionInteractor.currentKeyguardState,
+            shadeInteractor.isAnyFullyExpanded,
+        ) { currentKeyguardState, isShadeExpanded ->
+            (currentKeyguardState == GONE || currentKeyguardState == OCCLUDED) && !isShadeExpanded
+            // TODO(b/364360986): Add edge cases, like secure camera launch.
+        }
+
+    private val isHomeScreenStatusBarAllowed: Flow<Boolean> =
+        if (SceneContainerFlag.isEnabled) {
+            isHomeStatusBarAllowedByScene
+        } else {
+            isHomeScreenStatusBarAllowedLegacy
+        }
+
+    override val isClockVisible: Flow<VisibilityModel> =
+        combine(
+            isHomeScreenStatusBarAllowed,
+            collapsedStatusBarInteractor.visibilityViaDisableFlags,
+        ) { isStatusBarAllowed, visibilityViaDisableFlags ->
+            val showClock = isStatusBarAllowed && visibilityViaDisableFlags.isClockAllowed
+            // TODO(b/364360986): Take CollapsedStatusBarFragment.clockHiddenMode into account.
+            VisibilityModel(showClock.toVisibilityInt(), visibilityViaDisableFlags.animate)
+        }
+    override val isNotificationIconContainerVisible: Flow<VisibilityModel> =
+        combine(
+            isHomeScreenStatusBarAllowed,
+            collapsedStatusBarInteractor.visibilityViaDisableFlags,
+        ) { isStatusBarAllowed, visibilityViaDisableFlags ->
+            val showNotificationIconContainer =
+                isStatusBarAllowed && visibilityViaDisableFlags.areNotificationIconsAllowed
+            VisibilityModel(
+                showNotificationIconContainer.toVisibilityInt(),
+                visibilityViaDisableFlags.animate,
+            )
+        }
+    override val isSystemInfoVisible: Flow<VisibilityModel> =
+        combine(
+            isHomeScreenStatusBarAllowed,
+            collapsedStatusBarInteractor.visibilityViaDisableFlags,
+        ) { isStatusBarAllowed, visibilityViaDisableFlags ->
+            val showSystemInfo = isStatusBarAllowed && visibilityViaDisableFlags.isSystemInfoAllowed
+            VisibilityModel(showSystemInfo.toVisibilityInt(), visibilityViaDisableFlags.animate)
+        }
+
+    @View.Visibility
+    private fun Boolean.toVisibilityInt(): Int {
+        return if (this) View.VISIBLE else View.GONE
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index 1127f6f..caf09a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -216,7 +216,7 @@
             )
             return
         }
-        var stateAfter: String
+        val stateAfter: String
         if (entry in nextMap) {
             if (entry in nextMap) nextMap.remove(entry)
             if (entry in nextList) nextList.remove(entry)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
index 27bc6d3..76389f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
@@ -71,7 +71,7 @@
                         .semantics { stateDescription = viewModel.stateDescription },
                 verticalAlignment = Alignment.CenterVertically,
                 horizontalArrangement =
-                    Arrangement.spacedBy(space = 8.dp, alignment = Alignment.Start),
+                    Arrangement.spacedBy(space = 12.dp, alignment = Alignment.Start),
             ) {
                 Icon(icon = viewModel.icon, modifier = Modifier.size(24.dp))
                 Column {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
index 5953ea5..5392e38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
@@ -26,7 +26,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.systemui.Flags
 import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel
 
 @Composable
@@ -34,8 +33,8 @@
     val tiles by viewModel.tiles.collectAsStateWithLifecycle(initialValue = emptyList())
 
     LazyVerticalGrid(
-        columns = GridCells.Fixed(if (Flags.modesDialogSingleRows()) 1 else 2),
-        modifier = Modifier.fillMaxWidth().heightIn(max = 300.dp),
+        columns = GridCells.Fixed(1),
+        modifier = Modifier.fillMaxWidth().heightIn(max = 320.dp),
         verticalArrangement = Arrangement.spacedBy(8.dp),
         horizontalArrangement = Arrangement.spacedBy(8.dp),
     ) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt
index 8ecf250..2af84c7 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt
@@ -37,7 +37,7 @@
     @Application
     fun applicationScope(
         @Main dispatcherContext: CoroutineContext,
-    ): CoroutineScope = CoroutineScope(dispatcherContext)
+    ): CoroutineScope = CoroutineScope(dispatcherContext + createCoroutineTracingContext("ApplicationScope"))
 
     @Provides
     @Singleton
@@ -51,15 +51,7 @@
     @Provides
     @Singleton
     @Main
-    fun mainCoroutineContext(@Tracing tracingCoroutineContext: CoroutineContext): CoroutineContext {
-        return Dispatchers.Main.immediate + tracingCoroutineContext
-    }
-
-    @OptIn(ExperimentalCoroutinesApi::class)
-    @Provides
-    @Tracing
-    @Singleton
-    fun tracingCoroutineContext(): CoroutineContext {
-        return createCoroutineTracingContext()
+    fun mainCoroutineContext(): CoroutineContext {
+        return Dispatchers.Main.immediate
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
index a03221e..3c06828 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
@@ -91,10 +91,9 @@
     @Background
     @SysUISingleton
     fun bgCoroutineContext(
-        @Tracing tracingCoroutineContext: CoroutineContext,
         @Background bgCoroutineDispatcher: CoroutineDispatcher,
     ): CoroutineContext {
-        return bgCoroutineDispatcher + tracingCoroutineContext
+        return bgCoroutineDispatcher
     }
 
     /** Coroutine dispatcher for background operations on for UI. */
@@ -112,9 +111,8 @@
     @UiBackground
     @SysUISingleton
     fun uiBgCoroutineContext(
-        @Tracing tracingCoroutineContext: CoroutineContext,
         @UiBackground uiBgCoroutineDispatcher: CoroutineDispatcher,
     ): CoroutineContext {
-        return uiBgCoroutineDispatcher + tracingCoroutineContext
+        return uiBgCoroutineDispatcher
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
index 82f41a7..4d9aaa6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.util.settings
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import android.annotation.UserIdInt
 import android.content.ContentResolver
 import android.database.ContentObserver
@@ -93,7 +94,7 @@
      */
     @AnyThread
     fun registerContentObserverAsync(name: String, settingsObserver: ContentObserver) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-A")).launch {
             registerContentObserverSync(getUriFor(name), settingsObserver)
         }
 
@@ -110,7 +111,7 @@
         settingsObserver: ContentObserver,
         @WorkerThread registered: Runnable
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-B")).launch {
             registerContentObserverSync(getUriFor(name), settingsObserver)
             registered.run()
         }
@@ -143,7 +144,7 @@
      */
     @AnyThread
     fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-C")).launch {
             registerContentObserverSync(uri, settingsObserver)
         }
 
@@ -160,7 +161,7 @@
         settingsObserver: ContentObserver,
         @WorkerThread registered: Runnable
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-D")).launch {
             registerContentObserverSync(uri, settingsObserver)
             registered.run()
         }
@@ -205,7 +206,7 @@
         notifyForDescendants: Boolean,
         settingsObserver: ContentObserver
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-E")).launch {
             registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
         }
 
@@ -223,7 +224,7 @@
         settingsObserver: ContentObserver,
         @WorkerThread registered: Runnable
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-F")).launch {
             registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
             registered.run()
         }
@@ -274,7 +275,7 @@
         notifyForDescendants: Boolean,
         settingsObserver: ContentObserver
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-G")).launch {
             registerContentObserverSync(uri, notifyForDescendants, settingsObserver)
         }
 
@@ -292,7 +293,7 @@
         settingsObserver: ContentObserver,
         @WorkerThread registered: Runnable
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-H")).launch {
             registerContentObserverSync(uri, notifyForDescendants, settingsObserver)
             registered.run()
         }
@@ -329,7 +330,7 @@
      */
     @AnyThread
     fun unregisterContentObserverAsync(settingsObserver: ContentObserver) =
-        CoroutineScope(backgroundDispatcher).launch { unregisterContentObserver(settingsObserver) }
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-I")).launch { unregisterContentObserver(settingsObserver) }
 
     /**
      * Look up a name in the database.
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
index 8e3b813..c820c07 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.util.settings
 
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import android.annotation.UserIdInt
 import android.annotation.WorkerThread
 import android.content.ContentResolver
@@ -78,7 +79,7 @@
     }
 
     override fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-A")).launch {
             registerContentObserverForUserSync(uri, settingsObserver, userId)
         }
 
@@ -112,7 +113,7 @@
         notifyForDescendants: Boolean,
         settingsObserver: ContentObserver
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-B")).launch {
             registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId)
         }
 
@@ -157,7 +158,7 @@
         settingsObserver: ContentObserver,
         userHandle: Int
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-C")).launch {
             registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle)
         }
 
@@ -198,7 +199,7 @@
         settingsObserver: ContentObserver,
         userHandle: Int
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-D")).launch {
             registerContentObserverForUserSync(uri, settingsObserver, userHandle)
         }
 
@@ -215,7 +216,7 @@
         userHandle: Int,
         @WorkerThread registered: Runnable
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-E")).launch {
             registerContentObserverForUserSync(uri, settingsObserver, userHandle)
             registered.run()
         }
@@ -274,7 +275,7 @@
         settingsObserver: ContentObserver,
         userHandle: Int
     ) {
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-F")).launch {
             registerContentObserverForUserSync(
                 getUriFor(name),
                 notifyForDescendants,
@@ -338,7 +339,7 @@
         settingsObserver: ContentObserver,
         userHandle: Int
     ) =
-        CoroutineScope(backgroundDispatcher).launch {
+        CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-G")).launch {
             registerContentObserverForUserSync(
                 uri,
                 notifyForDescendants,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
rename to packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
index 5d76e32..85e8ab4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
@@ -22,10 +22,12 @@
 import android.graphics.Bitmap
 import android.net.Uri
 import android.os.PersistableBundle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.mockito.whenever
 import java.io.IOException
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
@@ -37,6 +39,7 @@
 import org.mockito.ArgumentMatchers.any
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -88,7 +91,8 @@
 
     @Test
     @Throws(IOException::class)
-    fun test_imageClipData() {
+    @DisableFlags(FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE)
+    fun test_imageClipData_legacy() {
         val testBitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888)
         whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
         whenever(mMockContext.resources).thenReturn(mContext.resources)
@@ -103,6 +107,21 @@
 
     @Test
     @Throws(IOException::class)
+    @EnableFlags(FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE)
+    fun test_imageClipData() {
+        val testBitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888)
+        whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
+        whenever(mMockContext.resources).thenReturn(mContext.resources)
+        whenever(mMockContentResolver.loadThumbnail(any(), any(), any())).thenReturn(testBitmap)
+        whenever(mMockContentResolver.getType(any())).thenReturn("text")
+        val imageClipData = ClipData("Test", arrayOf("image/png"), ClipData.Item(Uri.parse("test")))
+        val model = ClipboardModel.fromClipData(mMockContext, mClipboardUtils, imageClipData, "")
+        assertEquals(ClipboardModel.Type.IMAGE, model.type)
+        assertEquals(testBitmap, model.loadThumbnail(mMockContext))
+    }
+
+    @Test
+    @Throws(IOException::class)
     fun test_imageClipData_loadFailure() {
         whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
         whenever(mMockContext.resources).thenReturn(mContext.resources)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
index 6423d25..8d060e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
@@ -60,13 +60,13 @@
         onSetTiles: (List<TileSpec>) -> Unit,
     ) {
         DefaultEditTileGrid(
-            currentListState = listState,
+            listState = listState,
             otherTiles = listOf(),
             columns = 4,
             modifier = Modifier.fillMaxSize(),
             onRemoveTile = {},
             onSetTiles = onSetTiles,
-            onResize = {},
+            onResize = { _, _ -> },
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
index 682ed92..ee1c0e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
@@ -25,7 +25,11 @@
 import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.performClick
 import androidx.compose.ui.test.performCustomAccessibilityActionWithLabel
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeLeft
+import androidx.compose.ui.test.swipeRight
 import androidx.compose.ui.text.AnnotatedString
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -43,15 +47,19 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@OptIn(ExperimentalTestApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class ResizingTest : SysuiTestCase() {
     @get:Rule val composeRule = createComposeRule()
 
     @Composable
-    private fun EditTileGridUnderTest(listState: EditTileListState, onResize: (TileSpec) -> Unit) {
+    private fun EditTileGridUnderTest(
+        listState: EditTileListState,
+        onResize: (TileSpec, Boolean) -> Unit,
+    ) {
         DefaultEditTileGrid(
-            currentListState = listState,
+            listState = listState,
             otherTiles = listOf(),
             columns = 4,
             modifier = Modifier.fillMaxSize(),
@@ -61,22 +69,12 @@
         )
     }
 
-    @OptIn(ExperimentalTestApi::class)
     @Test
-    fun resizedIcon_shouldBeLarge() {
+    fun toggleIconTile_shouldBeLarge() {
         var tiles by mutableStateOf(TestEditTiles)
         val listState = EditTileListState(tiles, 4)
         composeRule.setContent {
-            EditTileGridUnderTest(listState) { spec ->
-                tiles =
-                    tiles.map {
-                        if (it.tile.tileSpec == spec) {
-                            toggleWidth(it)
-                        } else {
-                            it
-                        }
-                    }
-            }
+            EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
         }
         composeRule.waitForIdle()
 
@@ -87,22 +85,12 @@
         assertThat(tiles.find { it.tile.tileSpec.spec == "tileA" }?.width).isEqualTo(2)
     }
 
-    @OptIn(ExperimentalTestApi::class)
     @Test
-    fun resizedLarge_shouldBeIcon() {
+    fun toggleLargeTile_shouldBeIcon() {
         var tiles by mutableStateOf(TestEditTiles)
         val listState = EditTileListState(tiles, 4)
         composeRule.setContent {
-            EditTileGridUnderTest(listState) { spec ->
-                tiles =
-                    tiles.map {
-                        if (it.tile.tileSpec == spec) {
-                            toggleWidth(it)
-                        } else {
-                            it
-                        }
-                    }
-            }
+            EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
         }
         composeRule.waitForIdle()
 
@@ -113,9 +101,58 @@
         assertThat(tiles.find { it.tile.tileSpec.spec == "tileD_large" }?.width).isEqualTo(1)
     }
 
+    @Test
+    fun resizedLarge_shouldBeIcon() {
+        var tiles by mutableStateOf(TestEditTiles)
+        val listState = EditTileListState(tiles, 4)
+        composeRule.setContent {
+            EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
+        }
+        composeRule.waitForIdle()
+
+        composeRule
+            .onNodeWithContentDescription("tileA")
+            .performClick() // Select
+            .performTouchInput { // Resize down
+                swipeRight()
+            }
+        composeRule.waitForIdle()
+
+        assertThat(tiles.find { it.tile.tileSpec.spec == "tileA" }?.width).isEqualTo(1)
+    }
+
+    @Test
+    fun resizedIcon_shouldBeLarge() {
+        var tiles by mutableStateOf(TestEditTiles)
+        val listState = EditTileListState(tiles, 4)
+        composeRule.setContent {
+            EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
+        }
+        composeRule.waitForIdle()
+
+        composeRule
+            .onNodeWithContentDescription("tileD_large")
+            .performClick() // Select
+            .performTouchInput { // Resize down
+                swipeLeft()
+            }
+        composeRule.waitForIdle()
+
+        assertThat(tiles.find { it.tile.tileSpec.spec == "tileD_large" }?.width).isEqualTo(1)
+    }
+
     companion object {
-        private fun toggleWidth(tile: SizedTile<EditTileViewModel>): SizedTile<EditTileViewModel> {
-            return SizedTileImpl(tile.tile, width = if (tile.isIcon) 2 else 1)
+        private fun List<SizedTile<EditTileViewModel>>.resize(
+            spec: TileSpec,
+            toIcon: Boolean,
+        ): List<SizedTile<EditTileViewModel>> {
+            return map {
+                if (it.tile.tileSpec == spec) {
+                    SizedTileImpl(it.tile, width = if (toIcon) 1 else 2)
+                } else {
+                    it
+                }
+            }
         }
 
         private fun createEditTile(tileSpec: String): SizedTile<EditTileViewModel> {
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 4bd0c75..a6afd0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -453,6 +453,7 @@
                 mUiEventLogger,
                 () -> mKosmos.getInteractionJankMonitor(),
                 mJavaAdapter,
+                () -> mKeyguardInteractor,
                 () -> mKeyguardTransitionInteractor,
                 () -> mShadeInteractor,
                 () -> mKosmos.getDeviceUnlockedInteractor(),
@@ -611,6 +612,7 @@
                                 new UiEventLoggerFake(),
                                 () -> mKosmos.getInteractionJankMonitor(),
                                 mJavaAdapter,
+                                () -> mKeyguardInteractor,
                                 () -> mKeyguardTransitionInteractor,
                                 () -> mShadeInteractor,
                                 () -> mKosmos.getDeviceUnlockedInteractor(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt
index d2dfc92..907c684 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt
@@ -17,16 +17,19 @@
 import android.app.StatusBarManager.DISABLE2_NONE
 import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
 import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
+import android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS
 import android.app.StatusBarManager.DISABLE_CLOCK
 import android.app.StatusBarManager.DISABLE_NONE
 import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS
+import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS
+import android.app.StatusBarManager.DISABLE_SYSTEM_INFO
 import android.content.res.Configuration
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
 import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
@@ -82,7 +85,7 @@
     @Test
     fun disableFlags_initialValue_none() {
         assertThat(underTest.disableFlags.value)
-            .isEqualTo(DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE))
+            .isEqualTo(DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE, animate = false))
     }
 
     @Test
@@ -182,12 +185,7 @@
     fun disableFlags_quickSettingsDisabled_quickSettingsEnabledFalse() =
         testScope.runTest {
             getCommandQueueCallback()
-                .disable(
-                    DISPLAY_ID,
-                    DISABLE_NONE,
-                    DISABLE2_QUICK_SETTINGS,
-                    /* animate= */ false,
-                )
+                .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_QUICK_SETTINGS, /* animate= */ false)
 
             assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isFalse()
         }
@@ -217,21 +215,84 @@
             configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
             mContext.orCreateTestableResources.addOverride(
                 R.bool.config_use_split_notification_shade,
-                /* value= */ false
+                /* value= */ false,
             )
             remoteInputQuickSettingsDisabler.setRemoteInputActive(true)
             remoteInputQuickSettingsDisabler.onConfigChanged(configuration)
 
             getCommandQueueCallback()
+                .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_NONE, /* animate= */ false)
+
+            // THEN quick settings is disabled (even if the disable flags don't say so)
+            assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isFalse()
+        }
+
+    @Test
+    fun disableFlags_clockDisabled() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(DISPLAY_ID, DISABLE_CLOCK, DISABLE2_NONE, /* animate= */ false)
+
+            assertThat(underTest.disableFlags.value.isClockEnabled).isFalse()
+        }
+
+    @Test
+    fun disableFlags_clockEnabled() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_NONE, /* animate= */ false)
+
+            assertThat(underTest.disableFlags.value.isClockEnabled).isTrue()
+        }
+
+    @Test
+    fun disableFlags_notificationIconsDisabled() =
+        testScope.runTest {
+            getCommandQueueCallback()
                 .disable(
                     DISPLAY_ID,
-                    DISABLE_NONE,
+                    DISABLE_NOTIFICATION_ICONS,
                     DISABLE2_NONE,
                     /* animate= */ false,
                 )
 
-            // THEN quick settings is disabled (even if the disable flags don't say so)
-            assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isFalse()
+            assertThat(underTest.disableFlags.value.areNotificationIconsEnabled).isFalse()
+        }
+
+    @Test
+    fun disableFlags_notificationIconsEnabled() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_NONE, /* animate= */ false)
+
+            assertThat(underTest.disableFlags.value.areNotificationIconsEnabled).isTrue()
+        }
+
+    @Test
+    fun disableFlags_systemInfoDisabled_viaDisable1() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(DISPLAY_ID, DISABLE_SYSTEM_INFO, DISABLE2_NONE, /* animate= */ false)
+
+            assertThat(underTest.disableFlags.value.isSystemInfoEnabled).isFalse()
+        }
+
+    @Test
+    fun disableFlags_systemInfoDisabled_viaDisable2() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_SYSTEM_ICONS, /* animate= */ false)
+
+            assertThat(underTest.disableFlags.value.isSystemInfoEnabled).isFalse()
+        }
+
+    @Test
+    fun disableFlags_systemInfoEnabled() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_NONE, /* animate= */ false)
+
+            assertThat(underTest.disableFlags.value.isSystemInfoEnabled).isTrue()
         }
 
     @Test
@@ -267,6 +328,34 @@
             assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isFalse()
         }
 
+    @Test
+    fun disableFlags_animateFalse() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(
+                    DISPLAY_ID,
+                    DISABLE_NOTIFICATION_ALERTS,
+                    DISABLE2_NONE,
+                    /* animate= */ false,
+                )
+
+            assertThat(underTest.disableFlags.value.animate).isFalse()
+        }
+
+    @Test
+    fun disableFlags_animateTrue() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(
+                    DISPLAY_ID,
+                    DISABLE_NOTIFICATION_ALERTS,
+                    DISABLE2_NONE,
+                    /* animate= */ true,
+                )
+
+            assertThat(underTest.disableFlags.value.animate).isTrue()
+        }
+
     private fun getCommandQueueCallback(): CommandQueue.Callbacks {
         val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>()
         verify(commandQueue).addCallback(callbackCaptor.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 135fab8..63a560f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS;
 import static com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS;
+import static com.android.systemui.Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
 
@@ -157,6 +158,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void testDisableNone() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -167,6 +169,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void testDisableSystemInfo_systemAnimationIdle_doesHide() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -184,6 +187,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() {
         // GIVEN the status bar hides the system info via disable flags, while there is no event
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -213,6 +217,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() {
         // GIVEN the status bar hides the system info via disable flags, while there is no event
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -228,8 +233,8 @@
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
     }
 
-
     @Test
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() {
         // GIVEN the status bar is not disabled
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -245,6 +250,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() {
         // GIVEN the status bar is not disabled
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -268,6 +274,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void testDisableNotifications() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -285,6 +292,25 @@
     }
 
     @Test
+    @EnableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    public void testDisableNotifications_doesNothingWhenFlagEnabled() {
+        CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+        fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
+
+        assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
+
+        fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+        assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
+
+        fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
+
+        assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
+    }
+
+    @Test
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void testDisableClock() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -302,7 +328,26 @@
     }
 
     @Test
+    @EnableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    public void testDisableClock_doesNothingWhenFlagEnabled() {
+        CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+        fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_CLOCK, 0, false);
+
+        assertEquals(View.VISIBLE, getClockView().getVisibility());
+
+        fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+        assertEquals(View.VISIBLE, getClockView().getVisibility());
+
+        fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_CLOCK, 0, false);
+
+        assertEquals(View.VISIBLE, getClockView().getVisibility());
+    }
+
+    @Test
     @DisableSceneContainer
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void disable_shadeOpenAndShouldHide_everythingHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -320,6 +365,7 @@
 
     @Test
     @DisableSceneContainer
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void disable_shadeOpenButNotShouldHide_everythingShown() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -338,6 +384,7 @@
     /** Regression test for b/279790651. */
     @Test
     @DisableSceneContainer
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void disable_shadeOpenAndShouldHide_thenShadeNotOpenAndDozingUpdate_everythingShown() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -365,6 +412,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void disable_notTransitioningToOccluded_everythingShown() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -380,6 +428,7 @@
 
     @Test
     @DisableSceneContainer
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void disable_isTransitioningToOccluded_everythingHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -395,6 +444,7 @@
 
     @Test
     @DisableSceneContainer
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void disable_wasTransitioningToOccluded_transitionFinished_everythingShown() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -425,7 +475,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
     public void disable_noOngoingCall_chipHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -437,7 +487,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
     public void disable_hasOngoingCall_chipDisplayedAndNotificationIconsHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -450,7 +500,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
     public void disable_hasOngoingCallButNotificationIconsDisabled_chipHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -463,7 +513,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
     public void disable_hasOngoingCallButAlsoHun_chipHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -476,7 +526,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
     public void disable_ongoingCallEnded_chipHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -500,7 +550,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
     public void disable_hasOngoingCall_hidesNotifsWithoutAnimation() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         // Enable animations for testing so that we can verify we still aren't animating
@@ -517,7 +567,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
     public void screenSharingChipsDisabled_ignoresNewCallback() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -551,6 +601,7 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void noOngoingActivity_chipHidden() {
         resumeAndGetFragment();
 
@@ -568,6 +619,7 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void hasPrimaryOngoingActivity_primaryChipDisplayedAndNotificationIconsHidden() {
         resumeAndGetFragment();
 
@@ -581,8 +633,36 @@
     }
 
     @Test
+    @EnableFlags({
+            FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
+            FLAG_STATUS_BAR_RON_CHIPS,
+            FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    public void hasPrimaryOngoingActivity_viewsUnchangedWhenSimpleFragmentFlagOn() {
+        resumeAndGetFragment();
+
+        assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility());
+        assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
+
+        mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
+                /* hasPrimaryOngoingActivity= */ true,
+                /* hasSecondaryOngoingActivity= */ false,
+                /* shouldAnimate= */ false);
+
+        assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility());
+        assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
+
+        mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
+                /* hasPrimaryOngoingActivity= */ false,
+                /* hasSecondaryOngoingActivity= */ false,
+                /* shouldAnimate= */ false);
+
+        assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility());
+        assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
+    }
+
+    @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+    @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
     public void hasSecondaryOngoingActivity_butRonsFlagOff_secondaryChipHidden() {
         resumeAndGetFragment();
 
@@ -596,6 +676,7 @@
 
     @Test
     @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS})
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void hasSecondaryOngoingActivity_flagOn_secondaryChipShownAndNotificationIconsHidden() {
         resumeAndGetFragment();
 
@@ -610,7 +691,7 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+    @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
     public void hasOngoingActivityButNotificationIconsDisabled_chipHidden_ronsFlagOff() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -627,6 +708,7 @@
 
     @Test
     @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS})
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void hasOngoingActivitiesButNotificationIconsDisabled_chipsHidden_ronsFlagOn() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -644,7 +726,7 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+    @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
     public void hasOngoingActivityButAlsoHun_chipHidden_ronsFlagOff() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -661,6 +743,7 @@
 
     @Test
     @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS})
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void hasOngoingActivitiesButAlsoHun_chipsHidden_ronsFlagOn() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -678,7 +761,7 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+    @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
     public void primaryOngoingActivityEnded_chipHidden_ronsFlagOff() {
         resumeAndGetFragment();
 
@@ -701,6 +784,7 @@
 
     @Test
     @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS})
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void primaryOngoingActivityEnded_chipHidden_ronsFlagOn() {
         resumeAndGetFragment();
 
@@ -723,6 +807,7 @@
 
     @Test
     @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS})
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void secondaryOngoingActivityEnded_chipHidden() {
         resumeAndGetFragment();
 
@@ -745,7 +830,7 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+    @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
     public void hasOngoingActivity_hidesNotifsWithoutAnimation_ronsFlagOff() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         // Enable animations for testing so that we can verify we still aren't animating
@@ -764,6 +849,7 @@
 
     @Test
     @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS})
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void hasOngoingActivity_hidesNotifsWithoutAnimation_ronsFlagOn() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         // Enable animations for testing so that we can verify we still aren't animating
@@ -782,7 +868,7 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+    @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
     public void screenSharingChipsEnabled_ignoresOngoingCallController_ronsFlagOff() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -815,6 +901,7 @@
 
     @Test
     @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS})
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void screenSharingChipsEnabled_ignoresOngoingCallController_ronsFlagOn() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -848,6 +935,7 @@
 
     @Test
     @EnableSceneContainer
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void isHomeStatusBarAllowedByScene_false_everythingHidden() {
         resumeAndGetFragment();
 
@@ -861,6 +949,7 @@
 
     @Test
     @EnableSceneContainer
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void isHomeStatusBarAllowedByScene_true_everythingShown() {
         resumeAndGetFragment();
 
@@ -874,6 +963,7 @@
 
     @Test
     @EnableSceneContainer
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void disable_isHomeStatusBarAllowedBySceneFalse_disableValuesIgnored() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -891,6 +981,7 @@
 
     @Test
     @EnableSceneContainer
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void disable_isHomeStatusBarAllowedBySceneTrue_disableValuesUsed() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -908,6 +999,7 @@
 
     @Test
     @DisableSceneContainer
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void isHomeStatusBarAllowedByScene_sceneContainerDisabled_valueNotUsed() {
         resumeAndGetFragment();
 
@@ -921,6 +1013,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void disable_isDozing_clockAndSystemInfoVisible() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         when(mStatusBarStateController.isDozing()).thenReturn(true);
@@ -932,6 +1025,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void disable_NotDozing_clockAndSystemInfoVisible() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         when(mStatusBarStateController.isDozing()).thenReturn(false);
@@ -943,6 +1037,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void disable_headsUpShouldBeVisibleTrue_clockDisabled() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true);
@@ -953,6 +1048,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void disable_headsUpShouldBeVisibleFalse_clockNotDisabled() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(false);
@@ -1006,6 +1102,7 @@
 
     @Test
     @DisableSceneContainer
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void testStatusBarIcons_hiddenThroughoutCameraLaunch() {
         final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -1028,6 +1125,7 @@
 
     @Test
     @DisableSceneContainer
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void testStatusBarIcons_hiddenThroughoutLockscreenToDreamTransition() {
         final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -1063,6 +1161,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
     public void testStatusBarIcons_lockscreenToDreamTransitionButNotDreaming_iconsVisible() {
         final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
index 997c00c..c435d3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
@@ -16,10 +16,12 @@
 
 package com.android.systemui.statusbar.phone.fragment
 
+import android.platform.test.annotations.DisableFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.AnimatorTestRule
 import junit.framework.Assert.assertEquals
@@ -36,6 +38,7 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
+@DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
 class MultiSourceMinAlphaControllerTest : SysuiTestCase() {
 
     private val view = View(context)
@@ -60,7 +63,7 @@
         multiSourceMinAlphaController.animateToAlpha(
             alpha = 0.5f,
             sourceId = TEST_SOURCE_1,
-            duration = TEST_ANIMATION_DURATION
+            duration = TEST_ANIMATION_DURATION,
         )
         animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION)
         assertEquals(0.5f, view.alpha)
@@ -71,7 +74,7 @@
         multiSourceMinAlphaController.animateToAlpha(
             alpha = 0.5f,
             sourceId = TEST_SOURCE_1,
-            duration = TEST_ANIMATION_DURATION
+            duration = TEST_ANIMATION_DURATION,
         )
         multiSourceMinAlphaController.setAlpha(alpha = 0.7f, sourceId = TEST_SOURCE_2)
         multiSourceMinAlphaController.reset()
@@ -94,7 +97,7 @@
         multiSourceMinAlphaController.animateToAlpha(
             alpha = 0f,
             sourceId = TEST_SOURCE_1,
-            duration = TEST_ANIMATION_DURATION
+            duration = TEST_ANIMATION_DURATION,
         )
         animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION / 2)
         multiSourceMinAlphaController.setAlpha(alpha = 1f, sourceId = TEST_SOURCE_1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 4fd830d..a8bcfbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -756,6 +756,27 @@
             assertThat(latest!!.level).isEqualTo(4)
         }
 
+    @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+    @Test
+    fun satBasedIcon_reportsLevelZeroWhenOutOfService() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.signalLevelIcon)
+
+            // GIVEN a satellite connection
+            connectionRepository.isNonTerrestrial.value = true
+            // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH
+            connectionRepository.inflateSignalStrength.value = true
+
+            connectionRepository.primaryLevel.value = 4
+            assertThat(latest!!.level).isEqualTo(4)
+
+            connectionRepository.isInService.value = false
+            connectionRepository.primaryLevel.value = 4
+
+            // THEN level reports 0, by policy
+            assertThat(latest!!.level).isEqualTo(0)
+        }
+
     private fun createInteractor(
         overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
     ) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt
new file mode 100644
index 0000000..5036e77
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.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.statusbar.pipeline.shared.domain.interactor
+
+import android.app.StatusBarManager.DISABLE2_NONE
+import android.app.StatusBarManager.DISABLE_CLOCK
+import android.app.StatusBarManager.DISABLE_NONE
+import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS
+import android.app.StatusBarManager.DISABLE_SYSTEM_INFO
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+
+@SmallTest
+class CollapsedStatusBarInteractorTest : SysuiTestCase() {
+    val kosmos = testKosmos()
+    val testScope = kosmos.testScope
+    val disableFlagsRepo = kosmos.fakeDisableFlagsRepository
+
+    val underTest = kosmos.collapsedStatusBarInteractor
+
+    @Test
+    fun visibilityViaDisableFlags_allDisabled() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.visibilityViaDisableFlags)
+
+            disableFlagsRepo.disableFlags.value =
+                DisableFlagsModel(
+                    DISABLE_CLOCK or DISABLE_NOTIFICATION_ICONS or DISABLE_SYSTEM_INFO,
+                    DISABLE2_NONE,
+                    animate = false,
+                )
+
+            assertThat(latest!!.isClockAllowed).isFalse()
+            assertThat(latest!!.areNotificationIconsAllowed).isFalse()
+            assertThat(latest!!.isSystemInfoAllowed).isFalse()
+        }
+
+    @Test
+    fun visibilityViaDisableFlags_allEnabled() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.visibilityViaDisableFlags)
+
+            disableFlagsRepo.disableFlags.value =
+                DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE, animate = false)
+
+            assertThat(latest!!.isClockAllowed).isTrue()
+            assertThat(latest!!.areNotificationIconsAllowed).isTrue()
+            assertThat(latest!!.isSystemInfoAllowed).isTrue()
+        }
+
+    @Test
+    fun visibilityViaDisableFlags_animateFalse() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.visibilityViaDisableFlags)
+
+            disableFlagsRepo.disableFlags.value =
+                DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE, animate = false)
+
+            assertThat(latest!!.animate).isFalse()
+        }
+
+    @Test
+    fun visibilityViaDisableFlags_animateTrue() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.visibilityViaDisableFlags)
+
+            disableFlagsRepo.disableFlags.value =
+                DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE, animate = true)
+
+            assertThat(latest!!.animate).isTrue()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
index 7ae6ea5..bd85780 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
@@ -16,21 +16,27 @@
 
 package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
 
+import android.app.StatusBarManager.DISABLE2_NONE
+import android.app.StatusBarManager.DISABLE_CLOCK
+import android.app.StatusBarManager.DISABLE_NONE
+import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS
+import android.app.StatusBarManager.DISABLE_SYSTEM_INFO
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
+import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
@@ -38,27 +44,25 @@
 import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
 import com.android.systemui.scene.data.repository.sceneContainerRepository
-import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
-import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.screenrecord.data.model.ScreenRecordModel
 import com.android.systemui.screenrecord.data.repository.screenRecordRepository
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsScreenRecordChip
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsShareToAppChip
-import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
 import com.android.systemui.statusbar.data.model.StatusBarMode
 import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository.Companion.DISPLAY_ID
 import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
 import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
-import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
-import com.android.systemui.statusbar.phone.domain.interactor.lightsOutInteractor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.emptyFlow
@@ -83,21 +87,15 @@
     private val statusBarModeRepository = kosmos.fakeStatusBarModeRepository
     private val activeNotificationListRepository = kosmos.activeNotificationListRepository
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val disableFlagsRepository = kosmos.fakeDisableFlagsRepository
 
-    private val underTest =
-        CollapsedStatusBarViewModelImpl(
-            kosmos.lightsOutInteractor,
-            kosmos.activeNotificationsInteractor,
-            kosmos.keyguardTransitionInteractor,
-            kosmos.sceneInteractor,
-            kosmos.sceneContainerOcclusionInteractor,
-            kosmos.ongoingActivityChipsViewModel,
-            kosmos.applicationCoroutineScope,
-        )
+    private lateinit var underTest: CollapsedStatusBarViewModel
 
     @Before
     fun setUp() {
         setUpPackageManagerForMediaProjection(kosmos)
+        // Initialize here because some flags are checked when this class is constructed
+        underTest = kosmos.collapsedStatusBarViewModel
     }
 
     @Test
@@ -495,14 +493,272 @@
             assertThat(latest).isTrue()
         }
 
+    @Test
+    fun isClockVisible_allowedByDisableFlags_visible() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isClockVisible)
+            transitionKeyguardToGone()
+
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
+
+            assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
+        }
+
+    @Test
+    fun isClockVisible_notAllowedByDisableFlags_gone() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isClockVisible)
+            transitionKeyguardToGone()
+
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(DISABLE_CLOCK, DISABLE2_NONE)
+
+            assertThat(latest!!.visibility).isEqualTo(View.GONE)
+        }
+
+    @Test
+    fun isNotificationIconContainerVisible_allowedByDisableFlags_visible() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
+            transitionKeyguardToGone()
+
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
+
+            assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
+        }
+
+    @Test
+    fun isNotificationIconContainerVisible_notAllowedByDisableFlags_gone() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
+            transitionKeyguardToGone()
+
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(DISABLE_NOTIFICATION_ICONS, DISABLE2_NONE)
+
+            assertThat(latest!!.visibility).isEqualTo(View.GONE)
+        }
+
+    @Test
+    fun isSystemInfoVisible_allowedByDisableFlags_visible() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isSystemInfoVisible)
+            transitionKeyguardToGone()
+
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
+
+            assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
+        }
+
+    @Test
+    fun isSystemInfoVisible_notAllowedByDisableFlags_gone() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isSystemInfoVisible)
+            transitionKeyguardToGone()
+
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(DISABLE_SYSTEM_INFO, DISABLE2_NONE)
+
+            assertThat(latest!!.visibility).isEqualTo(View.GONE)
+        }
+
+    @Test
+    @DisableSceneContainer
+    fun lockscreenVisible_sceneFlagOff_noStatusBarViewsShown() =
+        testScope.runTest {
+            val clockVisible by collectLastValue(underTest.isClockVisible)
+            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
+            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.LOCKSCREEN,
+                testScope = this,
+            )
+
+            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun lockscreenVisible_sceneFlagOn_noStatusBarViewsShown() =
+        testScope.runTest {
+            val clockVisible by collectLastValue(underTest.isClockVisible)
+            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
+            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+
+            kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
+
+            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+        }
+
+    @Test
+    @DisableSceneContainer
+    fun bouncerVisible_sceneFlagOff_noStatusBarViewsShown() =
+        testScope.runTest {
+            val clockVisible by collectLastValue(underTest.isClockVisible)
+            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
+            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.PRIMARY_BOUNCER,
+                testScope = this,
+            )
+
+            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun bouncerVisible_sceneFlagOn_noStatusBarViewsShown() =
+        testScope.runTest {
+            val clockVisible by collectLastValue(underTest.isClockVisible)
+            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
+            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+
+            kosmos.sceneContainerRepository.snapToScene(Scenes.Bouncer)
+
+            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+        }
+
+    @Test
+    @DisableSceneContainer
+    fun keyguardIsOccluded_sceneFlagOff_statusBarViewsShown() =
+        testScope.runTest {
+            val clockVisible by collectLastValue(underTest.isClockVisible)
+            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
+            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.OCCLUDED,
+                testScope = this,
+            )
+
+            assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
+            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
+            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun keyguardIsOccluded_sceneFlagOn_statusBarViewsShown() =
+        testScope.runTest {
+            val clockVisible by collectLastValue(underTest.isClockVisible)
+            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
+            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+
+            kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, taskInfo = null)
+
+            assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
+            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
+            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
+        }
+
+    @Test
+    @DisableSceneContainer
+    fun keyguardNotShown_sceneFlagOff_statusBarViewsShown() =
+        testScope.runTest {
+            val clockVisible by collectLastValue(underTest.isClockVisible)
+            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
+            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+
+            transitionKeyguardToGone()
+
+            assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
+            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
+            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
+        }
+
+    @Test
+    @DisableSceneContainer
+    fun shadeNotShown_sceneFlagOff_statusBarViewsShown() =
+        testScope.runTest {
+            val clockVisible by collectLastValue(underTest.isClockVisible)
+            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
+            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+            transitionKeyguardToGone()
+
+            kosmos.shadeTestUtil.setShadeExpansion(0f)
+
+            assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
+            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
+            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun keyguardNotShownAndShadeNotShown_sceneFlagOn_statusBarViewsShown() =
+        testScope.runTest {
+            val clockVisible by collectLastValue(underTest.isClockVisible)
+            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
+            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+
+            kosmos.sceneContainerRepository.snapToScene(Scenes.Gone)
+
+            assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
+            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE)
+            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE)
+        }
+
+    @Test
+    @DisableSceneContainer
+    fun shadeShown_sceneFlagOff_noStatusBarViewsShown() =
+        testScope.runTest {
+            val clockVisible by collectLastValue(underTest.isClockVisible)
+            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
+            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+            transitionKeyguardToGone()
+
+            kosmos.shadeTestUtil.setShadeExpansion(1f)
+
+            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun shadeShown_sceneFlagOn_noStatusBarViewsShown() =
+        testScope.runTest {
+            val clockVisible by collectLastValue(underTest.isClockVisible)
+            val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
+            val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+            transitionKeyguardToGone()
+
+            kosmos.sceneContainerRepository.snapToScene(Scenes.Shade)
+
+            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+        }
+
     private fun activeNotificationsStore(notifications: List<ActiveNotificationModel>) =
         ActiveNotificationsStore.Builder()
             .apply { notifications.forEach(::addIndividualNotif) }
             .build()
 
     private val testNotifications =
-        listOf(
-            activeNotificationModel(key = "notif1"),
-            activeNotificationModel(key = "notif2"),
+        listOf(activeNotificationModel(key = "notif1"), activeNotificationModel(key = "notif2"))
+
+    private suspend fun transitionKeyguardToGone() {
+        keyguardTransitionRepository.sendTransitionSteps(
+            from = KeyguardState.LOCKSCREEN,
+            to = KeyguardState.GONE,
+            testScope = testScope,
         )
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
index 4834d36..cc90c11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
 
+import android.view.View
 import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import kotlinx.coroutines.flow.Flow
@@ -36,9 +37,29 @@
 
     override val isHomeStatusBarAllowedByScene = MutableStateFlow(false)
 
-    override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> = areNotificationLightsOut
+    override val isClockVisible =
+        MutableStateFlow(
+            CollapsedStatusBarViewModel.VisibilityModel(
+                visibility = View.GONE,
+                shouldAnimateChange = false,
+            )
+        )
 
-    fun setNotificationLightsOut(lightsOut: Boolean) {
-        areNotificationLightsOut.value = lightsOut
-    }
+    override val isNotificationIconContainerVisible =
+        MutableStateFlow(
+            CollapsedStatusBarViewModel.VisibilityModel(
+                visibility = View.GONE,
+                shouldAnimateChange = false,
+            )
+        )
+
+    override val isSystemInfoVisible =
+        MutableStateFlow(
+            CollapsedStatusBarViewModel.VisibilityModel(
+                visibility = View.GONE,
+                shouldAnimateChange = false,
+            )
+        )
+
+    override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> = areNotificationLightsOut
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt
index 8124224..3d41362 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt
@@ -16,9 +16,11 @@
 
 package com.android.systemui.communal.domain.interactor
 
+import android.service.dream.dreamManager
 import com.android.systemui.common.usagestats.domain.interactor.usageStatsInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.shared.system.taskStackChangeListeners
@@ -32,6 +34,8 @@
             keyguardTransitionInteractor = keyguardTransitionInteractor,
             taskStackChangeListeners = taskStackChangeListeners,
             usageStatsInteractor = usageStatsInteractor,
+            dreamManager = dreamManager,
+            bgScope = applicationCoroutineScope,
             logBuffer = logcatLogBuffer("WidgetTrampolineInteractor"),
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
index 64ae051..e6c98cd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.service.dream.dreamManager
+import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
@@ -39,10 +41,12 @@
             mainDispatcher = testDispatcher,
             keyguardInteractor = keyguardInteractor,
             glanceableHubTransitions = glanceableHubTransitions,
+            communalInteractor = communalInteractor,
             communalSceneInteractor = communalSceneInteractor,
             communalSettingsInteractor = communalSettingsInteractor,
             powerInteractor = powerInteractor,
             keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+            dreamManager = dreamManager,
             deviceEntryInteractor = deviceEntryInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
index 574bbcd..e2b283b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
@@ -39,5 +39,6 @@
             powerInteractor = powerInteractor,
             alternateBouncerInteractor = alternateBouncerInteractor,
             shadeInteractor = { shadeInteractor },
+            keyguardInteractor = { keyguardInteractor },
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
index 52416ba..ace1157 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
@@ -41,6 +41,5 @@
             trustRepository = trustRepository,
             alternateBouncerInteractor = alternateBouncerInteractor,
             powerInteractor = powerInteractor,
-            keyguardTransitionInteractor = keyguardTransitionInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..ef10459
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModelKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.dozingToGlanceableHubTransitionViewModel by Fixture {
+    DozingToGlanceableHubTransitionViewModel(animationFlow = keyguardTransitionAnimationFlow)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index 2deeb25..cfc31c7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.jank.interactionJankMonitor
 import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.scene.domain.interactor.sceneBackInteractor
@@ -36,6 +37,7 @@
             uiEventLogger,
             { interactionJankMonitor },
             mock(),
+            { keyguardInteractor },
             { keyguardTransitionInteractor },
             { shadeInteractor },
             { deviceUnlockedInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStateCallbackInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStateCallbackInteractorKosmos.kt
new file mode 100644
index 0000000..58dd522
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStateCallbackInteractorKosmos.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.keyguard.trustManager
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
+import com.android.systemui.keyguard.dismissCallbackRegistry
+import com.android.systemui.keyguard.domain.interactor.KeyguardStateCallbackInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.trustInteractor
+import com.android.systemui.keyguard.domain.interactor.windowManagerLockscreenVisibilityInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+
+val Kosmos.keyguardStateCallbackInteractor by
+    Kosmos.Fixture {
+        KeyguardStateCallbackInteractor(
+            applicationScope = testScope.backgroundScope,
+            backgroundDispatcher = testDispatcher,
+            selectedUserInteractor = selectedUserInteractor,
+            keyguardTransitionInteractor = keyguardTransitionInteractor,
+            trustInteractor = trustInteractor,
+            simBouncerInteractor = simBouncerInteractor,
+            dismissCallbackRegistry = dismissCallbackRegistry,
+            wmLockscreenVisibilityInteractor = windowManagerLockscreenVisibilityInteractor,
+            trustManager = trustManager,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index ffd8aab..a9e117a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.aodToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.aodToOccludedTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.dozingToGlanceableHubTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.dozingToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
@@ -66,6 +67,7 @@
         aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
         aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
         aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
+        dozingToGlanceableHubTransitionViewModel = dozingToGlanceableHubTransitionViewModel,
         dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
         dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
         dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorKosmos.kt
new file mode 100644
index 0000000..385a813
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
+
+val Kosmos.collapsedStatusBarInteractor: CollapsedStatusBarInteractor by
+    Kosmos.Fixture { CollapsedStatusBarInteractor(fakeDisableFlagsRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelKosmos.kt
new file mode 100644
index 0000000..1c7fd48
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelKosmos.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.phone.domain.interactor.lightsOutInteractor
+import com.android.systemui.statusbar.pipeline.shared.domain.interactor.collapsedStatusBarInteractor
+
+val Kosmos.collapsedStatusBarViewModel: CollapsedStatusBarViewModel by
+    Kosmos.Fixture {
+        CollapsedStatusBarViewModelImpl(
+            collapsedStatusBarInteractor,
+            lightsOutInteractor,
+            activeNotificationsInteractor,
+            keyguardTransitionInteractor,
+            sceneInteractor,
+            sceneContainerOcclusionInteractor,
+            shadeInteractor,
+            ongoingActivityChipsViewModel,
+            applicationCoroutineScope,
+        )
+    }
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 93cdde0..75d07bb 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -1037,14 +1037,12 @@
 
         @Override
         public void onCaptureFailed(int captureSequenceId, int reason) {
-            if (Flags.concertMode()) {
-                if (mCaptureCallback != null) {
-                    try {
-                        mCaptureCallback.onCaptureProcessFailed(captureSequenceId, reason);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Failed to notify capture failure due to remote " +
-                                "exception!");
-                    }
+            if (mCaptureCallback != null) {
+                try {
+                    mCaptureCallback.onCaptureProcessFailed(captureSequenceId, reason);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to notify capture failure due to remote " +
+                            "exception!");
                 }
             }
         }
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
index 1f98334..c3b7087 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
@@ -16,15 +16,7 @@
 
 package com.android.server.appfunctions;
 
-import android.annotation.NonNull;
-import android.os.UserHandle;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
-
 import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
@@ -41,50 +33,5 @@
                     /* unit= */ TimeUnit.SECONDS,
                     /* workQueue= */ new LinkedBlockingQueue<>());
 
-    /** A map of per-user executors for queued work. */
-    @GuardedBy("sLock")
-    private static final SparseArray<ExecutorService> mPerUserExecutorsLocked = new SparseArray<>();
-
-    private static final Object sLock = new Object();
-
-    /**
-     * Returns a per-user executor for queued metadata sync request.
-     *
-     * <p>The work submitted to these executor (Sync request) needs to be synchronous per user hence
-     * the use of a single thread.
-     *
-     * <p>Note: Use a different executor if not calling {@code submitSyncRequest} on a {@code
-     * MetadataSyncAdapter}.
-     */
-    // TODO(b/357551503): Restrict the scope of this executor to the MetadataSyncAdapter itself.
-    public static ExecutorService getPerUserSyncExecutor(@NonNull UserHandle user) {
-        synchronized (sLock) {
-            ExecutorService executor = mPerUserExecutorsLocked.get(user.getIdentifier(), null);
-            if (executor == null) {
-                executor = Executors.newSingleThreadExecutor();
-                mPerUserExecutorsLocked.put(user.getIdentifier(), executor);
-            }
-            return executor;
-        }
-    }
-
-    /**
-     * Shuts down and removes the per-user executor for queued work.
-     *
-     * <p>This should be called when the user is removed.
-     */
-    public static void shutDownAndRemoveUserExecutor(@NonNull UserHandle user)
-            throws InterruptedException {
-        ExecutorService executor;
-        synchronized (sLock) {
-            executor = mPerUserExecutorsLocked.get(user.getIdentifier());
-            mPerUserExecutorsLocked.remove(user.getIdentifier());
-        }
-        if (executor != null) {
-            executor.shutdown();
-            var unused = executor.awaitTermination(30, TimeUnit.SECONDS);
-        }
-    }
-
     private AppFunctionExecutors() {}
 }
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index b4713d9..1e723b5 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -95,12 +95,7 @@
     public void onUserStopping(@NonNull TargetUser user) {
         Objects.requireNonNull(user);
 
-        try {
-            AppFunctionExecutors.shutDownAndRemoveUserExecutor(user.getUserHandle());
-            MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
-        } catch (InterruptedException e) {
-            Slog.e(TAG, "Unable to remove data for: " + user.getUserHandle(), e);
-        }
+        MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
     }
 
     @Override
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index e29b6e4..d84b205 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -42,6 +42,7 @@
 import android.util.ArraySet;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.infra.AndroidFuture;
 import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults;
@@ -53,7 +54,9 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 
 /**
  * This class implements helper methods for synchronously interacting with AppSearch while
@@ -63,9 +66,15 @@
  */
 public class MetadataSyncAdapter {
     private static final String TAG = MetadataSyncAdapter.class.getSimpleName();
-    private final Executor mSyncExecutor;
+
+    private final ExecutorService mExecutor;
+
     private final AppSearchManager mAppSearchManager;
     private final PackageManager mPackageManager;
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private Future<?> mCurrentSyncTask;
 
     // Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility
     // by permissions.
@@ -73,12 +82,10 @@
     public static final int EXECUTE_APP_FUNCTIONS_TRUSTED = 10;
 
     public MetadataSyncAdapter(
-            @NonNull Executor syncExecutor,
-            @NonNull PackageManager packageManager,
-            @NonNull AppSearchManager appSearchManager) {
-        mSyncExecutor = Objects.requireNonNull(syncExecutor);
+            @NonNull PackageManager packageManager, @NonNull AppSearchManager appSearchManager) {
         mPackageManager = Objects.requireNonNull(packageManager);
         mAppSearchManager = Objects.requireNonNull(appSearchManager);
+        mExecutor = Executors.newSingleThreadExecutor();
     }
 
     /**
@@ -97,7 +104,7 @@
                                 AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB)
                         .build();
         AndroidFuture<Boolean> settableSyncStatus = new AndroidFuture<>();
-        mSyncExecutor.execute(
+        Runnable runnable =
                 () -> {
                     try (FutureAppSearchSession staticMetadataSearchSession =
                                     new FutureAppSearchSessionImpl(
@@ -117,10 +124,23 @@
                     } catch (Exception ex) {
                         settableSyncStatus.completeExceptionally(ex);
                     }
-                });
+                };
+
+        synchronized (mLock) {
+            if (mCurrentSyncTask != null && !mCurrentSyncTask.isDone()) {
+                var unused = mCurrentSyncTask.cancel(false);
+            }
+            mCurrentSyncTask = mExecutor.submit(runnable);
+        }
+
         return settableSyncStatus;
     }
 
+    /** This method shuts down the {@link MetadataSyncAdapter} scheduler. */
+    public void shutDown() {
+        mExecutor.shutdown();
+    }
+
     @WorkerThread
     @VisibleForTesting
     void trySyncAppFunctionMetadataBlocking(
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
index f421527..e933ec1 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
@@ -55,10 +55,7 @@
                 PackageManager perUserPackageManager = userContext.getPackageManager();
                 if (perUserAppSearchManager != null) {
                     metadataSyncAdapter =
-                            new MetadataSyncAdapter(
-                                    AppFunctionExecutors.getPerUserSyncExecutor(user),
-                                    perUserPackageManager,
-                                    perUserAppSearchManager);
+                            new MetadataSyncAdapter(perUserPackageManager, perUserAppSearchManager);
                     sPerUserMetadataSyncAdapter.put(user.getIdentifier(), metadataSyncAdapter);
                     return metadataSyncAdapter;
                 }
@@ -74,7 +71,12 @@
      */
     public static void removeUserSyncAdapter(UserHandle user) {
         synchronized (sLock) {
-            sPerUserMetadataSyncAdapter.remove(user.getIdentifier());
+            MetadataSyncAdapter metadataSyncAdapter =
+                    sPerUserMetadataSyncAdapter.get(user.getIdentifier(), null);
+            if (metadataSyncAdapter != null) {
+                metadataSyncAdapter.shutDown();
+                sPerUserMetadataSyncAdapter.remove(user.getIdentifier());
+            }
         }
     }
 }
diff --git a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index 46d60f9..0c54720 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -454,10 +454,10 @@
             @NonNull Associations associations)
             throws IOException {
         final XmlSerializer serializer = parent.startTag(null, XML_TAG_ASSOCIATIONS);
+        writeIntAttribute(serializer, XML_ATTR_MAX_ID, associations.getMaxId());
         for (AssociationInfo association : associations.getAssociations()) {
             writeAssociation(serializer, association);
         }
-        writeIntAttribute(serializer, XML_ATTR_MAX_ID, associations.getMaxId());
         serializer.endTag(null, XML_TAG_ASSOCIATIONS);
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 871c320..414a4e6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15304,10 +15304,8 @@
             }
 
             psr.setReportedForegroundServiceTypes(fgServiceTypes);
-            ProcessChangeItem item = mProcessList.enqueueProcessChangeItemLocked(
-                    proc.getPid(), proc.info.uid);
-            item.changes |= ProcessChangeItem.CHANGE_FOREGROUND_SERVICES;
-            item.foregroundServiceTypes = fgServiceTypes;
+            mProcessList.enqueueProcessChangeItemLocked(proc.getPid(), proc.info.uid,
+                    ProcessChangeItem.CHANGE_FOREGROUND_SERVICES, fgServiceTypes);
         }
         if (oomAdj) {
             updateOomAdjLocked(proc, OOM_ADJ_REASON_UI_VISIBILITY);
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
index f7085b4..15f1085 100644
--- a/services/core/java/com/android/server/am/BroadcastController.java
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -57,6 +57,7 @@
 import android.app.ApplicationThreadConstants;
 import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
+import android.app.BroadcastStickyCache;
 import android.app.IApplicationThread;
 import android.app.compat.CompatChanges;
 import android.appwidget.AppWidgetManager;
@@ -685,6 +686,7 @@
             boolean serialized, boolean sticky, int userId) {
         mService.enforceNotIsolatedCaller("broadcastIntent");
 
+        int result;
         synchronized (mService) {
             intent = verifyBroadcastLocked(intent);
 
@@ -706,7 +708,7 @@
 
             final long origId = Binder.clearCallingIdentity();
             try {
-                return broadcastIntentLocked(callerApp,
+                result = broadcastIntentLocked(callerApp,
                         callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
                         intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
                         resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
@@ -717,6 +719,10 @@
                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
+        if (sticky && result == ActivityManager.BROADCAST_SUCCESS) {
+            BroadcastStickyCache.incrementVersion(intent.getAction());
+        }
+        return result;
     }
 
     // Not the binder call surface
@@ -727,6 +733,7 @@
             boolean serialized, boolean sticky, int userId,
             BackgroundStartPrivileges backgroundStartPrivileges,
             @Nullable int[] broadcastAllowList) {
+        int result;
         synchronized (mService) {
             intent = verifyBroadcastLocked(intent);
 
@@ -734,7 +741,7 @@
             String[] requiredPermissions = requiredPermission == null ? null
                     : new String[] {requiredPermission};
             try {
-                return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
+                result = broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
                         resultToApp, resultTo, resultCode, resultData, resultExtras,
                         requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
                         uid, realCallingUid, realCallingPid, userId,
@@ -744,6 +751,10 @@
                 Binder.restoreCallingIdentity(origId);
             }
         }
+        if (sticky && result == ActivityManager.BROADCAST_SUCCESS) {
+            BroadcastStickyCache.incrementVersion(intent.getAction());
+        }
+        return result;
     }
 
     @GuardedBy("mService")
@@ -1442,6 +1453,7 @@
                     list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive,
                             callingUid, callerAppProcessState, resolvedType));
                 }
+                BroadcastStickyCache.incrementVersion(intent.getAction());
             }
         }
 
@@ -1708,6 +1720,7 @@
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
+        final ArrayList<String> changedStickyBroadcasts = new ArrayList<>();
         synchronized (mStickyBroadcasts) {
             ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
             if (stickies != null) {
@@ -1724,12 +1737,16 @@
                     if (list.size() <= 0) {
                         stickies.remove(intent.getAction());
                     }
+                    changedStickyBroadcasts.add(intent.getAction());
                 }
                 if (stickies.size() <= 0) {
                     mStickyBroadcasts.remove(userId);
                 }
             }
         }
+        for (int i = changedStickyBroadcasts.size() - 1; i >= 0; --i) {
+            BroadcastStickyCache.incrementVersionIfExists(changedStickyBroadcasts.get(i));
+        }
     }
 
     void finishReceiver(IBinder caller, int resultCode, String resultData,
@@ -1892,7 +1909,9 @@
 
     private void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
         mService.mProcessList.sendPackageBroadcastLocked(cmd, packages, userId);
-    }private List<ResolveInfo> collectReceiverComponents(
+    }
+
+    private List<ResolveInfo> collectReceiverComponents(
             Intent intent, String resolvedType, int callingUid, int callingPid,
             int[] users, int[] broadcastAllowList) {
         // TODO: come back and remove this assumption to triage all broadcasts
@@ -2108,9 +2127,18 @@
     }
 
     void removeStickyBroadcasts(int userId) {
+        final ArrayList<String> changedStickyBroadcasts = new ArrayList<>();
         synchronized (mStickyBroadcasts) {
+            final ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
+                    mStickyBroadcasts.get(userId);
+            if (stickies != null) {
+                changedStickyBroadcasts.addAll(stickies.keySet());
+            }
             mStickyBroadcasts.remove(userId);
         }
+        for (int i = changedStickyBroadcasts.size() - 1; i >= 0; --i) {
+            BroadcastStickyCache.incrementVersionIfExists(changedStickyBroadcasts.get(i));
+        }
     }
 
     @NeverCompile
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 22ec790..78a0a11 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -3617,14 +3617,12 @@
         if (changes != 0) {
             if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
                     "Changes in " + app + ": " + changes);
-            ActivityManagerService.ProcessChangeItem item =
-                    mProcessList.enqueueProcessChangeItemLocked(app.getPid(), app.info.uid);
-            item.changes |= changes;
-            item.foregroundActivities = state.hasRepForegroundActivities();
+            mProcessList.enqueueProcessChangeItemLocked(app.getPid(), app.info.uid,
+                    changes, state.hasRepForegroundActivities());
             if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
-                    "Item " + Integer.toHexString(System.identityHashCode(item))
-                            + " " + app.toShortString() + ": changes=" + item.changes
-                            + " foreground=" + item.foregroundActivities
+                    "Enqueued process change item for "
+                            + app.toShortString() + ": changes=" + changes
+                            + " foreground=" + state.hasRepForegroundActivities()
                             + " type=" + state.getAdjType() + " source=" + state.getAdjSource()
                             + " target=" + state.getAdjTarget());
         }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 00250b4..a93ae72 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -4998,53 +4998,70 @@
     }
 
     @GuardedBy("mService")
-    ProcessChangeItem enqueueProcessChangeItemLocked(int pid, int uid) {
+    void enqueueProcessChangeItemLocked(int pid, int uid, int changes, int foregroundServicetypes) {
         synchronized (mProcessChangeLock) {
-            int i = mPendingProcessChanges.size() - 1;
-            ActivityManagerService.ProcessChangeItem item = null;
-            while (i >= 0) {
-                item = mPendingProcessChanges.get(i);
-                if (item.pid == pid) {
-                    if (DEBUG_PROCESS_OBSERVERS) {
-                        Slog.i(TAG_PROCESS_OBSERVERS, "Re-using existing item: " + item);
-                    }
-                    break;
-                }
-                i--;
-            }
-
-            if (i < 0) {
-                // No existing item in pending changes; need a new one.
-                final int num = mAvailProcessChanges.size();
-                if (num > 0) {
-                    item = mAvailProcessChanges.remove(num - 1);
-                    if (DEBUG_PROCESS_OBSERVERS) {
-                        Slog.i(TAG_PROCESS_OBSERVERS, "Retrieving available item: " + item);
-                    }
-                } else {
-                    item = new ActivityManagerService.ProcessChangeItem();
-                    if (DEBUG_PROCESS_OBSERVERS) {
-                        Slog.i(TAG_PROCESS_OBSERVERS, "Allocating new item: " + item);
-                    }
-                }
-                item.changes = 0;
-                item.pid = pid;
-                item.uid = uid;
-                if (mPendingProcessChanges.size() == 0) {
-                    if (DEBUG_PROCESS_OBSERVERS) {
-                        Slog.i(TAG_PROCESS_OBSERVERS, "*** Enqueueing dispatch processes changed!");
-                    }
-                    mService.mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG)
-                            .sendToTarget();
-                }
-                mPendingProcessChanges.add(item);
-            }
-
-            return item;
+            final ProcessChangeItem item = enqueueProcessChangeItemLocked(pid, uid);
+            item.changes |= changes;
+            item.foregroundServiceTypes = foregroundServicetypes;
         }
     }
 
     @GuardedBy("mService")
+    void enqueueProcessChangeItemLocked(int pid, int uid, int changes,
+            boolean hasForegroundActivities) {
+        synchronized (mProcessChangeLock) {
+            final ProcessChangeItem item = enqueueProcessChangeItemLocked(pid, uid);
+            item.changes |= changes;
+            item.foregroundActivities = hasForegroundActivities;
+        }
+    }
+
+    @GuardedBy({"mService", "mProcessChangeLock"})
+    private ProcessChangeItem enqueueProcessChangeItemLocked(int pid, int uid) {
+        int i = mPendingProcessChanges.size() - 1;
+        ActivityManagerService.ProcessChangeItem item = null;
+        while (i >= 0) {
+            item = mPendingProcessChanges.get(i);
+            if (item.pid == pid) {
+                if (DEBUG_PROCESS_OBSERVERS) {
+                    Slog.i(TAG_PROCESS_OBSERVERS, "Re-using existing item: " + item);
+                }
+                break;
+            }
+            i--;
+        }
+
+        if (i < 0) {
+            // No existing item in pending changes; need a new one.
+            final int num = mAvailProcessChanges.size();
+            if (num > 0) {
+                item = mAvailProcessChanges.remove(num - 1);
+                if (DEBUG_PROCESS_OBSERVERS) {
+                    Slog.i(TAG_PROCESS_OBSERVERS, "Retrieving available item: " + item);
+                }
+            } else {
+                item = new ActivityManagerService.ProcessChangeItem();
+                if (DEBUG_PROCESS_OBSERVERS) {
+                    Slog.i(TAG_PROCESS_OBSERVERS, "Allocating new item: " + item);
+                }
+            }
+            item.changes = 0;
+            item.pid = pid;
+            item.uid = uid;
+            if (mPendingProcessChanges.size() == 0) {
+                if (DEBUG_PROCESS_OBSERVERS) {
+                    Slog.i(TAG_PROCESS_OBSERVERS, "*** Enqueueing dispatch processes changed!");
+                }
+                mService.mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG)
+                        .sendToTarget();
+            }
+            mPendingProcessChanges.add(item);
+        }
+
+        return item;
+    }
+
+    @GuardedBy("mService")
     void scheduleDispatchProcessDiedLocked(int pid, int uid) {
         synchronized (mProcessChangeLock) {
             for (int i = mPendingProcessChanges.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 6ae6f3d..6af4be5 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -619,7 +619,7 @@
             this.op = op;
             this.uid = uid;
             this.uidState = uidState;
-            this.packageName = packageName;
+            this.packageName = packageName.intern();
             // We keep an invariant that the persistent device will always have an entry in
             // mDeviceAttributedOps.
             mDeviceAttributedOps.put(PERSISTENT_DEVICE_ID_DEFAULT,
@@ -1031,7 +1031,7 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            String pkgName = intent.getData().getEncodedSchemeSpecificPart();
+            String pkgName = intent.getData().getEncodedSchemeSpecificPart().intern();
             int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
 
             if (action.equals(ACTION_PACKAGE_ADDED)
@@ -1235,7 +1235,7 @@
         Ops ops = uidState.pkgOps.get(packageName);
         if (ops == null) {
             ops = new Ops(packageName, uidState);
-            uidState.pkgOps.put(packageName, ops);
+            uidState.pkgOps.put(packageName.intern(), ops);
         }
 
         SparseIntArray packageModes =
@@ -4739,7 +4739,7 @@
                 return null;
             }
             ops = new Ops(packageName, uidState);
-            uidState.pkgOps.put(packageName, ops);
+            uidState.pkgOps.put(packageName.intern(), ops);
         }
 
         if (edit) {
@@ -5076,7 +5076,7 @@
         Ops ops = uidState.pkgOps.get(pkgName);
         if (ops == null) {
             ops = new Ops(pkgName, uidState);
-            uidState.pkgOps.put(pkgName, ops);
+            uidState.pkgOps.put(pkgName.intern(), ops);
         }
         ops.put(op.op, op);
     }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 55d9c6e..0fd22c5 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -17,6 +17,11 @@
 
 import static android.media.audio.Flags.scoManagedByAudio;
 
+import static com.android.media.audio.Flags.equalScoLeaVcIndexRange;
+import static com.android.server.audio.AudioService.BT_COMM_DEVICE_ACTIVE_BLE_HEADSET;
+import static com.android.server.audio.AudioService.BT_COMM_DEVICE_ACTIVE_BLE_SPEAKER;
+import static com.android.server.audio.AudioService.BT_COMM_DEVICE_ACTIVE_SCO;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.compat.CompatChanges;
@@ -64,6 +69,7 @@
 import android.util.PrintWriterPrinter;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.server.audio.AudioService.BtCommDeviceActiveType;
 import com.android.server.utils.EventLogger;
 
 import java.io.PrintWriter;
@@ -333,8 +339,8 @@
             Log.v(TAG, "setCommunicationDevice, device: " + device + ", uid: " + uid);
         }
 
-        synchronized (mDeviceStateLock) {
-            if (device == null) {
+        if (device == null) {
+            synchronized (mDeviceStateLock) {
                 CommunicationRouteClient client = getCommunicationRouteClientForUid(uid);
                 if (client == null) {
                     return false;
@@ -835,15 +841,15 @@
         return isDeviceOnForCommunication(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
     }
 
-    /*package*/ boolean isBluetoothScoActive() {
+    private boolean isBluetoothScoActive() {
         return isDeviceActiveForCommunication(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
     }
 
-    /*package*/ boolean isBluetoothBleHeadsetActive() {
+    private boolean isBluetoothBleHeadsetActive() {
         return isDeviceActiveForCommunication(AudioDeviceInfo.TYPE_BLE_HEADSET);
     }
 
-    /*package*/ boolean isBluetoothBleSpeakerActive() {
+    private boolean isBluetoothBleSpeakerActive() {
         return isDeviceActiveForCommunication(AudioDeviceInfo.TYPE_BLE_SPEAKER);
     }
 
@@ -1437,7 +1443,20 @@
         }
         mCurCommunicationPortId = portId;
 
-        mAudioService.postScoDeviceActive(isBluetoothScoActive());
+        @BtCommDeviceActiveType int btCommDeviceActiveType = 0;
+        if (equalScoLeaVcIndexRange()) {
+            if (isBluetoothScoActive()) {
+                btCommDeviceActiveType = BT_COMM_DEVICE_ACTIVE_SCO;
+            } else if (isBluetoothBleHeadsetActive()) {
+                btCommDeviceActiveType = BT_COMM_DEVICE_ACTIVE_BLE_HEADSET;
+            } else if (isBluetoothBleSpeakerActive()) {
+                btCommDeviceActiveType = BT_COMM_DEVICE_ACTIVE_BLE_SPEAKER;
+            }
+            mAudioService.postBtCommDeviceActive(btCommDeviceActiveType);
+        } else {
+            mAudioService.postBtCommDeviceActive(
+                    isBluetoothScoActive() ? BT_COMM_DEVICE_ACTIVE_SCO : btCommDeviceActiveType);
+        }
 
         final int nbDispatchers = mCommDevDispatchers.beginBroadcast();
         for (int i = 0; i < nbDispatchers; i++) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9e11081..e83b036 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -66,6 +66,7 @@
 import static com.android.media.audio.Flags.asDeviceConnectionFailure;
 import static com.android.media.audio.Flags.audioserverPermissions;
 import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
+import static com.android.media.audio.Flags.equalScoLeaVcIndexRange;
 import static com.android.media.audio.Flags.replaceStreamBtSco;
 import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
 import static com.android.media.audio.Flags.setStreamVolumeOrder;
@@ -470,7 +471,7 @@
     private static final int MSG_CONFIGURATION_CHANGED = 54;
     private static final int MSG_BROADCAST_MASTER_MUTE = 55;
     private static final int MSG_UPDATE_CONTEXTUAL_VOLUMES = 56;
-    private static final int MSG_SCO_DEVICE_ACTIVE_UPDATE = 57;
+    private static final int MSG_BT_COMM_DEVICE_ACTIVE_UPDATE = 57;
 
     /**
      * Messages handled by the {@link SoundDoseHelper}, do not exceed
@@ -766,7 +767,21 @@
      * @see System#MUTE_STREAMS_AFFECTED */
     private int mUserMutableStreams;
 
-    private final AtomicBoolean mScoDeviceActive = new AtomicBoolean(false);
+    /** The active bluetooth device type used for communication is sco. */
+    /*package*/ static final int BT_COMM_DEVICE_ACTIVE_SCO = 1;
+    /** The active bluetooth device type used for communication is ble headset. */
+    /*package*/ static final int BT_COMM_DEVICE_ACTIVE_BLE_HEADSET = 1 << 1;
+    /** The active bluetooth device type used for communication is ble speaker. */
+    /*package*/ static final int BT_COMM_DEVICE_ACTIVE_BLE_SPEAKER = 1 << 2;
+    @IntDef({
+            BT_COMM_DEVICE_ACTIVE_SCO, BT_COMM_DEVICE_ACTIVE_BLE_HEADSET,
+            BT_COMM_DEVICE_ACTIVE_BLE_SPEAKER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BtCommDeviceActiveType {
+    }
+
+    private final AtomicInteger mBtCommDeviceActive = new AtomicInteger(0);
 
     @NonNull
     private SoundEffectsHelper mSfxHelper;
@@ -2522,12 +2537,18 @@
                 // this should not happen, throwing exception
                 throw new IllegalArgumentException("STREAM_BLUETOOTH_SCO is deprecated");
             }
-            return streamType == AudioSystem.STREAM_VOICE_CALL && mScoDeviceActive.get();
+            return streamType == AudioSystem.STREAM_VOICE_CALL
+                    && mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO;
         } else {
             return streamType == AudioSystem.STREAM_BLUETOOTH_SCO;
         }
     }
 
+    private boolean isStreamBluetoothComm(int streamType) {
+        return (streamType == AudioSystem.STREAM_VOICE_CALL && mBtCommDeviceActive.get() != 0)
+                || streamType == AudioSystem.STREAM_BLUETOOTH_SCO;
+    }
+
     private void dumpStreamStates(PrintWriter pw) {
         pw.println("\nStream volumes (device: index)");
         int numStreamTypes = AudioSystem.getNumStreamTypes();
@@ -4761,7 +4782,7 @@
                 + asDeviceConnectionFailure());
         pw.println("\tandroid.media.audio.autoPublicVolumeApiHardening:"
                 + autoPublicVolumeApiHardening());
-        pw.println("\tandroid.media.audio.Flags.automaticBtDeviceType:"
+        pw.println("\tandroid.media.audio.automaticBtDeviceType:"
                 + automaticBtDeviceType());
         pw.println("\tandroid.media.audio.featureSpatialAudioHeadtrackingLowLatency:"
                 + featureSpatialAudioHeadtrackingLowLatency());
@@ -4783,6 +4804,8 @@
                 + absVolumeIndexFix());
         pw.println("\tcom.android.media.audio.replaceStreamBtSco:"
                 + replaceStreamBtSco());
+        pw.println("\tcom.android.media.audio.equalScoLeaVcIndexRange:"
+                + equalScoLeaVcIndexRange());
     }
 
     private void dumpAudioMode(PrintWriter pw) {
@@ -4896,7 +4919,7 @@
         final VolumeStreamState streamState = getVssForStreamOrDefault(streamTypeAlias);
 
         if (!replaceStreamBtSco() && (streamType == AudioManager.STREAM_VOICE_CALL)
-                && isInCommunication() && mDeviceBroker.isBluetoothScoActive()) {
+                && isInCommunication() && mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) {
             Log.i(TAG, "setStreamVolume for STREAM_VOICE_CALL, switching to STREAM_BLUETOOTH_SCO");
             streamType = AudioManager.STREAM_BLUETOOTH_SCO;
         }
@@ -5947,10 +5970,10 @@
         final boolean ringerModeMute = ringerMode == AudioManager.RINGER_MODE_VIBRATE
                 || ringerMode == AudioManager.RINGER_MODE_SILENT;
         final boolean shouldRingSco = ringerMode == AudioManager.RINGER_MODE_VIBRATE
-                && mDeviceBroker.isBluetoothScoActive();
+                && mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO;
         final boolean shouldRingBle = ringerMode == AudioManager.RINGER_MODE_VIBRATE
-                && (mDeviceBroker.isBluetoothBleHeadsetActive()
-                || mDeviceBroker.isBluetoothBleSpeakerActive());
+                && (mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_BLE_HEADSET
+                || mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_BLE_SPEAKER);
         // Ask audio policy engine to force use Bluetooth SCO/BLE channel if needed
         final String eventSource = "muteRingerModeStreams() from u/pid:" + Binder.getCallingUid()
                 + "/" + Binder.getCallingPid();
@@ -7419,7 +7442,8 @@
         case AudioSystem.PLATFORM_VOICE:
             if (isInCommunication()
                     || mAudioSystem.isStreamActive(AudioManager.STREAM_VOICE_CALL, 0)) {
-                if (!replaceStreamBtSco() && mDeviceBroker.isBluetoothScoActive()) {
+                if (!replaceStreamBtSco()
+                        && mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) {
                     if (DEBUG_VOL) {
                         Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO...");
                     }
@@ -7463,7 +7487,8 @@
             }
         default:
             if (isInCommunication()) {
-                if (!replaceStreamBtSco() && mDeviceBroker.isBluetoothScoActive()) {
+                if (!replaceStreamBtSco()
+                        && mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) {
                     if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO");
                     return AudioSystem.STREAM_BLUETOOTH_SCO;
                 } else {
@@ -7788,15 +7813,15 @@
                 0 /*delay*/);
     }
 
-    /*package*/ void postScoDeviceActive(boolean scoDeviceActive) {
+    /*package*/ void postBtCommDeviceActive(@BtCommDeviceActiveType int btCommDeviceActive) {
         sendMsg(mAudioHandler,
-                MSG_SCO_DEVICE_ACTIVE_UPDATE,
-                SENDMSG_QUEUE, scoDeviceActive ? 1 : 0 /*arg1*/, 0 /*arg2*/, null /*obj*/,
+                MSG_BT_COMM_DEVICE_ACTIVE_UPDATE,
+                SENDMSG_QUEUE, btCommDeviceActive /*arg1*/, 0 /*arg2*/, null /*obj*/,
                 0 /*delay*/);
     }
 
-    private void onUpdateScoDeviceActive(boolean scoDeviceActive) {
-        if (mScoDeviceActive.compareAndSet(!scoDeviceActive, scoDeviceActive)) {
+    private void onUpdateBtCommDeviceActive(@BtCommDeviceActiveType int btCommDeviceActive) {
+        if (mBtCommDeviceActive.getAndSet(btCommDeviceActive) != btCommDeviceActive) {
             getVssForStreamOrDefault(AudioSystem.STREAM_VOICE_CALL).updateIndexFactors();
         }
     }
@@ -8997,7 +9022,7 @@
         }
 
         public void updateIndexFactors() {
-            if (!replaceStreamBtSco()) {
+            if (!replaceStreamBtSco() && !equalScoLeaVcIndexRange()) {
                 return;
             }
 
@@ -9008,10 +9033,18 @@
                         mIndexMax = MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10;
                     }
 
-                    // SCO devices have a different min index
-                    if (isStreamBluetoothSco(mStreamType)) {
+                    if (!equalScoLeaVcIndexRange() && isStreamBluetoothSco(mStreamType)) {
+                        // SCO devices have a different min index
                         mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10;
                         mIndexStepFactor = 1.f;
+                    } else if (equalScoLeaVcIndexRange() && isStreamBluetoothComm(mStreamType)) {
+                        // For non SCO devices the stream state does not change the min index
+                        if (mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) {
+                            mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10;
+                        } else {
+                            mIndexMin = MIN_STREAM_VOLUME[mStreamType] * 10;
+                        }
+                        mIndexStepFactor = 1.f;
                     } else {
                         mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL] * 10;
                         mIndexStepFactor = (float) (mIndexMax - mIndexMin) / (float) (
@@ -9207,7 +9240,7 @@
         private void setStreamVolumeIndex(int index, int device) {
             // Only set audio policy BT SCO stream volume to 0 when the stream is actually muted.
             // This allows RX path muting by the audio HAL only when explicitly muted but not when
-            // index is just set to 0 to repect BT requirements
+            // index is just set to 0 to respect BT requirements
             if (isStreamBluetoothSco(mStreamType) && index == 0 && !isFullyMuted()) {
                 index = 1;
             }
@@ -10217,8 +10250,8 @@
                     onUpdateContextualVolumes();
                     break;
 
-                case MSG_SCO_DEVICE_ACTIVE_UPDATE:
-                    onUpdateScoDeviceActive(msg.arg1 != 0);
+                case MSG_BT_COMM_DEVICE_ACTIVE_UPDATE:
+                    onUpdateBtCommDeviceActive(msg.arg1);
                     break;
 
                 case MusicFxHelper.MSG_EFFECT_CLIENT_GONE:
@@ -10809,7 +10842,8 @@
             //TODO move inside HardeningEnforcer after refactor that moves permission checks
             //     in the blockFocusMethod
             if (permissionOverridesCheck) {
-                mHardeningEnforcer.metricsLogFocusReq(/*blocked*/false, focusReqType, uid);
+                mHardeningEnforcer.metricsLogFocusReq(/*blocked*/ false, focusReqType, uid,
+                        /*unblockedBySdk*/ false);
             }
             if (!permissionOverridesCheck && mHardeningEnforcer.blockFocusMethod(uid,
                     HardeningEnforcer.METHOD_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS,
diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java
index faeba5d..6611110 100644
--- a/services/core/java/com/android/server/audio/HardeningEnforcer.java
+++ b/services/core/java/com/android/server/audio/HardeningEnforcer.java
@@ -168,6 +168,8 @@
         }
 
         boolean blocked = true;
+        // indicates the focus request was not blocked because of the SDK version
+        boolean unblockedBySdk = false;
         if (noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, callingUid, packageName, attributionTag)) {
             if (DEBUG) {
                 Slog.i(TAG, "blockFocusMethod pack:" + packageName + " NOT blocking");
@@ -179,9 +181,10 @@
                         + targetSdk);
             }
             blocked = false;
+            unblockedBySdk = true;
         }
 
-        metricsLogFocusReq(blocked, focusReqType, callingUid);
+        metricsLogFocusReq(blocked, focusReqType, callingUid, unblockedBySdk);
 
         if (!blocked) {
             return false;
@@ -195,7 +198,16 @@
         return true;
     }
 
-    /*package*/ void metricsLogFocusReq(boolean blocked, int focusReq, int callingUid) {
+    /**
+     * Log metrics for the focus request
+     * @param blocked true if the call blocked
+     * @param focusReq the type of focus request
+     * @param callingUid the UID of the caller
+     * @param unblockedBySdk if blocked is false,
+     *                       true indicates it was unblocked thanks to an older SDK
+     */
+    /*package*/ void metricsLogFocusReq(boolean blocked, int focusReq, int callingUid,
+            boolean unblockedBySdk) {
         final String metricId = blocked ? METRIC_COUNTERS_FOCUS_DENIAL.get(focusReq)
                 : METRIC_COUNTERS_FOCUS_GRANT.get(focusReq);
         if (TextUtils.isEmpty(metricId)) {
@@ -204,6 +216,12 @@
         }
         try {
             Counter.logIncrementWithUid(metricId, callingUid);
+            if (!blocked && unblockedBySdk) {
+                // additional metric to capture focus requests that are currently granted
+                // because the app is on an older SDK, but would have been blocked otherwise
+                Counter.logIncrementWithUid(
+                        "media_audio.value_audio_focus_grant_hardening_waived_by_sdk", callingUid);
+            }
         } catch (Exception e) {
             Slog.e(TAG, "Counter error metricId:" + metricId + " for focus req:" + focusReq
                     + " from uid:" + callingUid, e);
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 5d85089..2d802b2 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -49,7 +49,6 @@
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.biometrics.SensorPropertiesInternal;
-import android.hardware.biometrics.face.IFace;
 import android.hardware.face.FaceSensorConfigurations;
 import android.hardware.face.FaceSensorProperties;
 import android.hardware.face.FaceSensorPropertiesInternal;
@@ -73,6 +72,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.SystemService;
+import com.android.server.biometrics.sensors.face.FaceService;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintService;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 
@@ -211,7 +211,7 @@
          */
         @VisibleForTesting
         public String[] getFaceAidlInstances() {
-            return ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
+            return FaceService.getDeclaredInstances();
         }
 
         /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index bd6d593..8c98872 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.MANAGE_FACE;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.hardware.face.FaceSensorConfigurations.getIFace;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -60,6 +61,7 @@
 import android.view.Surface;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.SystemService;
@@ -753,7 +755,7 @@
     public FaceService(Context context) {
         this(context, null /* faceProviderFunction */, () -> IBiometricService.Stub.asInterface(
                 ServiceManager.getService(Context.BIOMETRIC_SERVICE)), null /* faceProvider */,
-                () -> ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR));
+                () -> getDeclaredInstances());
     }
 
     @VisibleForTesting FaceService(Context context,
@@ -778,8 +780,7 @@
 
         mFaceProvider = faceProvider != null ? faceProvider : (name) -> {
             final String fqName = IFace.DESCRIPTOR + "/" + name;
-            final IFace face = IFace.Stub.asInterface(
-                    Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
+            final IFace face = getIFace(fqName);
             if (face == null) {
                 Slog.e(TAG, "Unable to get declared service: " + fqName);
                 return null;
@@ -835,6 +836,23 @@
      */
     public static native void releaseSurfaceHandle(@NonNull NativeHandle handle);
 
+    /**
+     * Get all face hal instances declared in manifest
+     * @return instance names
+     */
+    public static String[] getDeclaredInstances() {
+        String[] a = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
+        Slog.i(TAG, "Before:getDeclaredInstances: IFace instance found, a.length="
+                + a.length);
+        if (!ArrayUtils.contains(a, "virtual")) {
+            // Now, the virtual hal is registered with IVirtualHal interface and it is also
+            //   moved from vendor to system_ext partition without a device manifest. So
+            //   if the old vhal is not declared, add here.
+            a = ArrayUtils.appendElement(String.class, a, "virtual");
+        }
+        Slog.i(TAG, "After:getDeclaredInstances: a.length=" + a.length);
+        return a;
+    }
 
     void syncEnrollmentsNow() {
         Utils.checkPermissionOrShell(getContext(), MANAGE_FACE);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index dca1491..3ed01d5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -23,6 +23,9 @@
 import android.hardware.biometrics.face.AuthenticationFrame;
 import android.hardware.biometrics.face.BaseFrame;
 import android.hardware.biometrics.face.EnrollmentFrame;
+import android.hardware.biometrics.face.virtualhal.AcquiredInfoAndVendorCode;
+import android.hardware.biometrics.face.virtualhal.EnrollmentProgressStep;
+import android.hardware.biometrics.face.virtualhal.NextEnrollment;
 import android.hardware.face.Face;
 import android.hardware.face.FaceAuthenticationFrame;
 import android.hardware.face.FaceEnrollFrame;
@@ -50,6 +53,7 @@
 public class BiometricTestSessionImpl extends ITestSession.Stub {
 
     private static final String TAG = "face/aidl/BiometricTestSessionImpl";
+    private static final int VHAL_ENROLLMENT_ID = 9999;
 
     @NonNull private final Context mContext;
     private final int mSensorId;
@@ -144,16 +148,35 @@
 
         super.setTestHalEnabled_enforcePermission();
 
-        mProvider.setTestHalEnabled(enabled);
         mSensor.setTestHalEnabled(enabled);
+        mProvider.setTestHalEnabled(enabled);
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
     @Override
-    public void startEnroll(int userId) {
+    public void startEnroll(int userId) throws RemoteException {
 
         super.startEnroll_enforcePermission();
 
+        Slog.i(TAG, "startEnroll(): isVhalForTesting=" + mProvider.isVhalForTesting());
+        if (mProvider.isVhalForTesting()) {
+            final AcquiredInfoAndVendorCode[] acquiredInfoAndVendorCodes =
+                    {new AcquiredInfoAndVendorCode()};
+            final EnrollmentProgressStep[] enrollmentProgressSteps =
+                    {new EnrollmentProgressStep(), new EnrollmentProgressStep()};
+            enrollmentProgressSteps[0].durationMs = 100;
+            enrollmentProgressSteps[0].acquiredInfoAndVendorCodes = acquiredInfoAndVendorCodes;
+            enrollmentProgressSteps[1].durationMs = 200;
+            enrollmentProgressSteps[1].acquiredInfoAndVendorCodes = acquiredInfoAndVendorCodes;
+
+            final NextEnrollment nextEnrollment = new NextEnrollment();
+            nextEnrollment.id = VHAL_ENROLLMENT_ID;
+            nextEnrollment.progressSteps = enrollmentProgressSteps;
+            nextEnrollment.result = true;
+            mProvider.getVhal().setNextEnrollment(nextEnrollment);
+            mProvider.getVhal().setOperationAuthenticateDuration(6000);
+        }
+
         mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
                 mContext.getOpPackageName(), new int[0] /* disabledFeatures */,
                 null /* previewSurface */, false /* debugConsent */,
@@ -166,6 +189,10 @@
 
         super.finishEnroll_enforcePermission();
 
+        if (mProvider.isVhalForTesting()) {
+            return;
+        }
+
         int nextRandomId = mRandom.nextInt();
         while (mEnrollmentIds.contains(nextRandomId)) {
             nextRandomId = mRandom.nextInt();
@@ -178,11 +205,16 @@
 
     @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
     @Override
-    public void acceptAuthentication(int userId)  {
+    public void acceptAuthentication(int userId) throws RemoteException {
 
         // Fake authentication with any of the existing faces
         super.acceptAuthentication_enforcePermission();
 
+        if (mProvider.isVhalForTesting()) {
+            mProvider.getVhal().setEnrollmentHit(VHAL_ENROLLMENT_ID);
+            return;
+        }
+
         List<Face> faces = FaceUtils.getInstance(mSensorId)
                 .getBiometricsForUser(mContext, userId);
         if (faces.isEmpty()) {
@@ -196,10 +228,15 @@
 
     @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
     @Override
-    public void rejectAuthentication(int userId)  {
+    public void rejectAuthentication(int userId) throws RemoteException {
 
         super.rejectAuthentication_enforcePermission();
 
+        if (mProvider.isVhalForTesting()) {
+            mProvider.getVhal().setEnrollmentHit(VHAL_ENROLLMENT_ID + 1);
+            return;
+        }
+
         mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFailed();
     }
 
@@ -236,11 +273,17 @@
 
     @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
     @Override
-    public void cleanupInternalState(int userId)  {
+    public void cleanupInternalState(int userId) throws RemoteException {
 
         super.cleanupInternalState_enforcePermission();
 
         Slog.d(TAG, "cleanupInternalState: " + userId);
+
+        if (mProvider.isVhalForTesting()) {
+            Slog.i(TAG, "cleanup virtualhal configurations");
+            mProvider.getVhal().resetConfigurations(); //setEnrollments(new int[]{});
+        }
+
         mProvider.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
             @Override
             public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index bb213bf..5127e68 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -16,6 +16,9 @@
 
 package com.android.server.biometrics.sensors.face.aidl;
 
+import static android.hardware.face.FaceSensorConfigurations.getIFace;
+import static android.hardware.face.FaceSensorConfigurations.remapFqName;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -32,6 +35,7 @@
 import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.SensorProps;
+import android.hardware.biometrics.face.virtualhal.IVirtualHal;
 import android.hardware.face.Face;
 import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.face.FaceEnrollOptions;
@@ -54,6 +58,7 @@
 import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.BiometricDanglingReceiver;
 import com.android.server.biometrics.BiometricHandlerProvider;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
@@ -130,6 +135,11 @@
     private AuthenticationStatsCollector mAuthenticationStatsCollector;
     @Nullable
     private IFace mDaemon;
+    @Nullable
+    private IVirtualHal mVhal;
+    @Nullable
+    private String mHalInstanceNameCurrent;
+
 
     private final class BiometricTaskStackListener extends TaskStackListener {
         @Override
@@ -286,14 +296,37 @@
         if (mTestHalEnabled) {
             return true;
         }
-        return ServiceManager.checkService(IFace.DESCRIPTOR + "/" + mHalInstanceName) != null;
+        return ServiceManager.checkService(
+                remapFqName(IFace.DESCRIPTOR + "/" + mHalInstanceName)) != null;
     }
 
     @Nullable
     @VisibleForTesting
     synchronized IFace getHalInstance() {
         if (mTestHalEnabled) {
-            return new TestHal();
+            if (Flags.useVhalForTesting()) {
+                if (!mHalInstanceNameCurrent.contains("virtual")) {
+                    Slog.i(getTag(), "Switching face hal from " + mHalInstanceName
+                            + " to virtual hal");
+                    mHalInstanceNameCurrent = "virtual";
+                    mDaemon = null;
+                }
+            } else {
+                // Enabling the test HAL for a single sensor in a multi-sensor HAL currently enables
+                // the test HAL for all sensors under that HAL. This can be updated in the future if
+                // necessary.
+                return new TestHal();
+            }
+        } else {
+            if (mHalInstanceNameCurrent == null) {
+                mHalInstanceNameCurrent = mHalInstanceName;
+            } else if (mHalInstanceNameCurrent.contains("virtual")
+                    && mHalInstanceNameCurrent != mHalInstanceName) {
+                Slog.i(getTag(), "Switching face from virtual hal " + "to "
+                        + mHalInstanceName);
+                mHalInstanceNameCurrent = mHalInstanceName;
+                mDaemon = null;
+            }
         }
 
         if (mDaemon != null) {
@@ -302,10 +335,7 @@
 
         Slog.d(getTag(), "Daemon was null, reconnecting");
 
-        mDaemon = IFace.Stub.asInterface(
-                Binder.allowBlocking(
-                        ServiceManager.waitForDeclaredService(
-                                IFace.DESCRIPTOR + "/" + mHalInstanceName)));
+        mDaemon = getIFace(IFace.DESCRIPTOR + "/" + mHalInstanceNameCurrent);
         if (mDaemon == null) {
             Slog.e(getTag(), "Unable to get daemon");
             return null;
@@ -833,7 +863,13 @@
     }
 
     void setTestHalEnabled(boolean enabled) {
+        final boolean changed = enabled != mTestHalEnabled;
         mTestHalEnabled = enabled;
+        Slog.i(getTag(), "setTestHalEnabled(): isVhalForTestingFlags=" + Flags.useVhalForTesting()
+                + " mTestHalEnabled=" + mTestHalEnabled + " changed=" + changed);
+        if (changed && isVhalForTesting()) {
+            getHalInstance();
+        }
     }
 
     @Override
@@ -851,9 +887,40 @@
     }
 
     /**
+     * Return true if vhal_for_testing feature is enabled and test is active
+     */
+    public boolean isVhalForTesting() {
+        return (Flags.useVhalForTesting() && mTestHalEnabled);
+    }
+
+
+    /**
      * Sends a face re enroll notification.
      */
     public void sendFaceReEnrollNotification() {
         mAuthenticationStatsCollector.sendFaceReEnrollNotification();
     }
+
+    /**
+     * Sends a fingerprint enroll notification.
+     */
+    public void sendFingerprintReEnrollNotification() {
+        mAuthenticationStatsCollector.sendFingerprintReEnrollNotification();
+    }
+
+    /**
+     * Return virtual hal AIDL interface if it is used for testing
+     *
+     */
+    public IVirtualHal getVhal() throws RemoteException {
+        if (mVhal == null && isVhalForTesting()) {
+            mVhal = IVirtualHal.Stub.asInterface(
+                    Binder.allowBlocking(
+                            ServiceManager.waitForService(
+                                    IVirtualHal.DESCRIPTOR + "/"
+                                            + mHalInstanceNameCurrent)));
+            Slog.d(getTag(), "getVhal " + mHalInstanceNameCurrent);
+        }
+        return mVhal;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 6f95349..9fddcfc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -17,6 +17,7 @@
 package com.android.server.biometrics.sensors.face.aidl;
 
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE;
+import static android.hardware.face.FaceSensorConfigurations.remapFqName;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -337,7 +338,8 @@
         if (mTestHalEnabled) {
             return true;
         }
-        return ServiceManager.checkService(IFace.DESCRIPTOR + "/" + halInstanceName) != null;
+        return ServiceManager.checkService(
+                remapFqName(IFace.DESCRIPTOR + "/" + halInstanceName)) != null;
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e7fd8f7..ae33b83 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -571,6 +571,10 @@
     private final DisplayNotificationManager mDisplayNotificationManager;
     private final ExternalDisplayStatsService mExternalDisplayStatsService;
 
+    // Manages the relative placement of extended displays
+    @Nullable
+    private final DisplayTopologyCoordinator mDisplayTopologyCoordinator;
+
     /**
      * Applications use {@link android.view.Display#getRefreshRate} and
      * {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate.
@@ -644,6 +648,11 @@
         mDisplayNotificationManager = new DisplayNotificationManager(mFlags, mContext,
                 mExternalDisplayStatsService);
         mExternalDisplayPolicy = new ExternalDisplayPolicy(new ExternalDisplayPolicyInjector());
+        if (mFlags.isDisplayTopologyEnabled()) {
+            mDisplayTopologyCoordinator = new DisplayTopologyCoordinator();
+        } else {
+            mDisplayTopologyCoordinator = null;
+        }
     }
 
     public void setupSchedulerPolicies() {
@@ -3474,9 +3483,13 @@
             mSmallAreaDetectionController.dump(pw);
         }
 
+        if (mDisplayTopologyCoordinator != null) {
+            pw.println();
+            mDisplayTopologyCoordinator.dump(pw);
+        }
+
         pw.println();
         mFlags.dump(pw);
-
     }
 
     private static float[] getFloatArray(TypedArray array) {
diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
new file mode 100644
index 0000000..631f147
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
@@ -0,0 +1,104 @@
+/*
+ * 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;
+
+import android.annotation.Nullable;
+import android.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class manages the relative placement (topology) of extended displays. It is responsible for
+ * updating and persisting the topology.
+ */
+class DisplayTopologyCoordinator {
+
+    /**
+     * The topology tree
+     */
+    @Nullable
+    private TopologyTreeNode mRoot;
+
+    /**
+     * The logical display ID of the primary display that will show certain UI elements.
+     * This is not necessarily the same as the default display.
+     */
+    private int mPrimaryDisplayId;
+
+    /**
+     * Print the object's state and debug information into the given stream.
+     * @param pw The stream to dump information to.
+     */
+    public void dump(PrintWriter pw) {
+        pw.println("DisplayTopologyCoordinator:");
+        pw.println("--------------------");
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+        ipw.increaseIndent();
+
+        ipw.println("mPrimaryDisplayId: " + mPrimaryDisplayId);
+
+        ipw.println("Topology tree:");
+        if (mRoot != null) {
+            ipw.increaseIndent();
+            mRoot.dump(ipw);
+            ipw.decreaseIndent();
+        }
+    }
+
+    private static class TopologyTreeNode {
+
+        /**
+         * The logical display ID
+         */
+        private int mDisplayId;
+
+        private final List<TopologyTreeNode> mChildren = new ArrayList<>();
+
+        /**
+         * The position of this display relative to its parent.
+         */
+        private Position mPosition;
+
+        /**
+         * The distance from the top edge of the parent display to the top edge of this display (in
+         * case of POSITION_LEFT or POSITION_RIGHT) or from the left edge of the parent display
+         * to the left edge of this display (in case of POSITION_TOP or POSITION_BOTTOM). The unit
+         * used is density-independent pixels (dp).
+         */
+        private double mOffset;
+
+        /**
+         * Print the object's state and debug information into the given stream.
+         * @param ipw The stream to dump information to.
+         */
+        void dump(IndentingPrintWriter ipw) {
+            ipw.println("Display {id=" + mDisplayId + ", position=" + mPosition
+                    + ", offset=" + mOffset + "}");
+            ipw.increaseIndent();
+            for (TopologyTreeNode child : mChildren) {
+                child.dump(ipw);
+            }
+            ipw.decreaseIndent();
+        }
+
+        private enum Position {
+            POSITION_LEFT, POSITION_TOP, POSITION_RIGHT, POSITION_BOTTOM
+        }
+    }
+}
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 f600e7f..df66893 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -69,6 +69,10 @@
             Flags.FLAG_ENABLE_MODE_LIMIT_FOR_EXTERNAL_DISPLAY,
             Flags::enableModeLimitForExternalDisplay);
 
+    private final FlagState mDisplayTopology = new FlagState(
+            Flags.FLAG_DISPLAY_TOPOLOGY,
+            Flags::displayTopology);
+
     private final FlagState mConnectedDisplayErrorHandlingFlagState = new FlagState(
             Flags.FLAG_ENABLE_CONNECTED_DISPLAY_ERROR_HANDLING,
             Flags::enableConnectedDisplayErrorHandling);
@@ -266,6 +270,10 @@
         return mExternalDisplayLimitModeState.isEnabled();
     }
 
+    public boolean isDisplayTopologyEnabled() {
+        return mDisplayTopology.isEnabled();
+    }
+
     /**
      * @return Whether displays refresh rate synchronization is enabled.
      */
@@ -441,6 +449,7 @@
         pw.println(" " + mConnectedDisplayManagementFlagState);
         pw.println(" " + mDisplayOffloadFlagState);
         pw.println(" " + mExternalDisplayLimitModeState);
+        pw.println(" " + mDisplayTopology);
         pw.println(" " + mHdrClamperFlagState);
         pw.println(" " + mNbmControllerFlagState);
         pw.println(" " + mPowerThrottlingClamperFlagState);
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 9968ba5..e3ebe5b 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
@@ -92,6 +92,14 @@
 }
 
 flag {
+    name: "display_topology"
+    namespace: "display_manager"
+    description: "Display topology for moving cursors and windows between extended displays"
+    bug: "278199220"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "enable_displays_refresh_rates_synchronization"
     namespace: "display_manager"
     description: "Enables synchronization of refresh rates across displays"
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 101596d..aae7b59 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -261,6 +261,7 @@
                 .setDisplayName(HdmiUtils.getDefaultDeviceName(source))
                 .setDeviceType(deviceTypes.get(0))
                 .setVendorId(Constants.VENDOR_ID_UNKNOWN)
+                .setPortId(mService.getHdmiCecNetwork().physicalAddressToPortId(physicalAddress))
                 .build();
         mService.getHdmiCecNetwork().addCecDevice(newDevice);
     }
@@ -1433,6 +1434,7 @@
     protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
         assertRunOnServiceThread();
         mService.unregisterTvInputCallback(mTvInputCallback);
+        mTvInputs.clear();
         // Remove any repeated working actions.
         // HotplugDetectionAction will be reinstated during the wake up process.
         // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
diff --git a/services/core/java/com/android/server/input/KeyRemapper.java b/services/core/java/com/android/server/input/KeyRemapper.java
index 7ba7769..82b36af 100644
--- a/services/core/java/com/android/server/input/KeyRemapper.java
+++ b/services/core/java/com/android/server/input/KeyRemapper.java
@@ -17,27 +17,24 @@
 package com.android.server.input;
 
 import android.content.Context;
-import android.hardware.input.InputManager;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.util.ArrayMap;
 import android.util.FeatureFlagUtils;
-import android.view.InputDevice;
 
 import com.android.internal.annotations.GuardedBy;
 
 import java.util.Map;
-import java.util.Objects;
 
 /**
  * A component of {@link InputManagerService} responsible for managing key remappings.
  *
  * @hide
  */
-final class KeyRemapper implements InputManager.InputDeviceListener {
+final class KeyRemapper {
 
-    private static final int MSG_UPDATE_EXISTING_DEVICES = 1;
+    private static final int MSG_UPDATE_EXISTING_KEY_REMAPPING = 1;
     private static final int MSG_REMAP_KEY = 2;
     private static final int MSG_CLEAR_ALL_REMAPPING = 3;
 
@@ -49,7 +46,7 @@
     private final Handler mHandler;
 
     KeyRemapper(Context context, NativeInputManagerService nativeService,
-            PersistentDataStore dataStore, Looper looper) {
+                PersistentDataStore dataStore, Looper looper) {
         mContext = context;
         mNative = nativeService;
         mDataStore = dataStore;
@@ -57,13 +54,7 @@
     }
 
     public void systemRunning() {
-        InputManager inputManager = Objects.requireNonNull(
-                mContext.getSystemService(InputManager.class));
-        inputManager.registerInputDeviceListener(this, mHandler);
-
-        Message msg = Message.obtain(mHandler, MSG_UPDATE_EXISTING_DEVICES,
-                inputManager.getInputDeviceIds());
-        mHandler.sendMessage(msg);
+        Message.obtain(mHandler, MSG_UPDATE_EXISTING_KEY_REMAPPING).sendToTarget();
     }
 
     public void remapKey(int fromKey, int toKey) {
@@ -91,19 +82,19 @@
         }
     }
 
-    private void addKeyRemapping(int fromKey, int toKey) {
-        InputManager inputManager = Objects.requireNonNull(
-                mContext.getSystemService(InputManager.class));
-        for (int deviceId : inputManager.getInputDeviceIds()) {
-            InputDevice inputDevice = inputManager.getInputDevice(deviceId);
-            if (inputDevice != null && !inputDevice.isVirtual() && inputDevice.isFullKeyboard()) {
-                mNative.addKeyRemapping(deviceId, fromKey, toKey);
-            }
+    private void setKeyRemapping(Map<Integer, Integer> keyRemapping) {
+        int index = 0;
+        int[] fromKeycodesArr = new int[keyRemapping.size()];
+        int[] toKeycodesArr = new int[keyRemapping.size()];
+        for (Map.Entry<Integer, Integer> entry : keyRemapping.entrySet()) {
+            fromKeycodesArr[index] = entry.getKey();
+            toKeycodesArr[index] = entry.getValue();
+            index++;
         }
+        mNative.setKeyRemapping(fromKeycodesArr, toKeycodesArr);
     }
 
     private void remapKeyInternal(int fromKey, int toKey) {
-        addKeyRemapping(fromKey, toKey);
         synchronized (mDataStore) {
             try {
                 if (fromKey == toKey) {
@@ -114,6 +105,7 @@
             } finally {
                 mDataStore.saveIfNeeded();
             }
+            setKeyRemapping(mDataStore.getKeyRemapping());
         }
     }
 
@@ -123,45 +115,25 @@
                 Map<Integer, Integer> keyRemapping = mDataStore.getKeyRemapping();
                 for (int fromKey : keyRemapping.keySet()) {
                     mDataStore.clearMappedKey(fromKey);
-
-                    // Remapping to itself will clear the remapping on native side
-                    addKeyRemapping(fromKey, fromKey);
                 }
             } finally {
                 mDataStore.saveIfNeeded();
             }
+            setKeyRemapping(mDataStore.getKeyRemapping());
         }
     }
 
-    @Override
-    public void onInputDeviceAdded(int deviceId) {
+    public void updateExistingKeyMapping() {
         if (!supportRemapping()) {
             return;
         }
-        InputManager inputManager = Objects.requireNonNull(
-                mContext.getSystemService(InputManager.class));
-        InputDevice inputDevice = inputManager.getInputDevice(deviceId);
-        if (inputDevice != null && !inputDevice.isVirtual() && inputDevice.isFullKeyboard()) {
-            Map<Integer, Integer> remapping = getKeyRemapping();
-            remapping.forEach(
-                    (fromKey, toKey) -> mNative.addKeyRemapping(deviceId, fromKey, toKey));
-        }
-    }
-
-    @Override
-    public void onInputDeviceRemoved(int deviceId) {
-    }
-
-    @Override
-    public void onInputDeviceChanged(int deviceId) {
+        setKeyRemapping(getKeyRemapping());
     }
 
     private boolean handleMessage(Message msg) {
         switch (msg.what) {
-            case MSG_UPDATE_EXISTING_DEVICES:
-                for (int deviceId : (int[]) msg.obj) {
-                    onInputDeviceAdded(deviceId);
-                }
+            case MSG_UPDATE_EXISTING_KEY_REMAPPING:
+                updateExistingKeyMapping();
                 return true;
             case MSG_REMAP_KEY:
                 remapKeyInternal(msg.arg1, msg.arg2);
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 5dd461d..d17e256 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -48,7 +48,7 @@
 
     int getSwitchState(int deviceId, int sourceMask, int sw);
 
-    void addKeyRemapping(int deviceId, int fromKeyCode, int toKeyCode);
+    void setKeyRemapping(int[] fromKeyCodes, int[] toKeyCodes);
 
     boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
 
@@ -311,7 +311,7 @@
         public native int getSwitchState(int deviceId, int sourceMask, int sw);
 
         @Override
-        public native void addKeyRemapping(int deviceId, int fromKeyCode, int toKeyCode);
+        public native void setKeyRemapping(int[] fromKeyCodes, int[] toKeyCodes);
 
         @Override
         public native boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes,
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
index 0e940d2..a1e5ebc 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
@@ -24,7 +24,6 @@
 import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
-import android.hardware.input.InputManager;
 import android.util.Slog;
 import android.util.TypedValue;
 import android.view.Gravity;
@@ -42,6 +41,7 @@
 import com.android.server.input.TouchpadHardwareState;
 
 import java.util.Objects;
+import java.util.function.Consumer;
 
 public class TouchpadDebugView extends LinearLayout {
     private static final float MAX_SCREEN_WIDTH_PROPORTION = 0.4f;
@@ -52,7 +52,8 @@
     private static final float DEFAULT_RES_Y = 45f;
     private static final int TEXT_PADDING_DP = 12;
     private static final int ROUNDED_CORNER_RADIUS_DP = 24;
-
+    private static final int BUTTON_PRESSED_BACKGROUND_COLOR = Color.rgb(118, 151, 99);
+    private static final int BUTTON_RELEASED_BACKGROUND_COLOR = Color.rgb(84, 85, 169);
     /**
      * Input device ID for the touchpad that this debug view is displaying.
      */
@@ -74,22 +75,24 @@
     private int mWindowLocationBeforeDragX;
     private int mWindowLocationBeforeDragY;
     private int mLatestGestureType = 0;
+    private TouchpadSelectionView mTouchpadSelectionView;
+    private TouchpadVisualizationView mTouchpadVisualizationView;
     private TextView mGestureInfoView;
     @NonNull
     private TouchpadHardwareState mLastTouchpadState =
             new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
                     new TouchpadFingerState[0]);
-    private TouchpadVisualizationView mTouchpadVisualizationView;
     private final TouchpadHardwareProperties mTouchpadHardwareProperties;
 
     public TouchpadDebugView(Context context, int touchpadId,
-                             TouchpadHardwareProperties touchpadHardwareProperties) {
+                             TouchpadHardwareProperties touchpadHardwareProperties,
+                             Consumer<Integer> touchpadSwitchHandler) {
         super(context);
         mTouchpadId = touchpadId;
         mWindowManager =
                 Objects.requireNonNull(getContext().getSystemService(WindowManager.class));
         mTouchpadHardwareProperties = touchpadHardwareProperties;
-        init(context, touchpadId);
+        init(context, touchpadId, touchpadSwitchHandler);
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
 
         mWindowLayoutParams = new WindowManager.LayoutParams();
@@ -111,7 +114,8 @@
         mWindowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
     }
 
-    private void init(Context context, int touchpadId) {
+    private void init(Context context, int touchpadId,
+                      Consumer<Integer> touchpadSwitchHandler) {
         updateScreenDimensions();
         setOrientation(VERTICAL);
         setLayoutParams(new LayoutParams(
@@ -119,35 +123,31 @@
                 LayoutParams.WRAP_CONTENT));
         setBackgroundColor(Color.TRANSPARENT);
 
-        TextView nameView = new TextView(context);
-        nameView.setBackgroundColor(Color.RED);
-        nameView.setTextSize(TEXT_SIZE_SP);
-        nameView.setText(Objects.requireNonNull(Objects.requireNonNull(
-                        mContext.getSystemService(InputManager.class))
-                .getInputDevice(touchpadId)).getName());
-        nameView.setGravity(Gravity.CENTER);
-        nameView.setTextColor(Color.WHITE);
+        mTouchpadSelectionView = new TouchpadSelectionView(context,
+                touchpadId, touchpadSwitchHandler);
+        mTouchpadSelectionView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR);
+        mTouchpadSelectionView.setGravity(Gravity.CENTER);
         int paddingInDP = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, TEXT_PADDING_DP,
                 getResources().getDisplayMetrics());
-        nameView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP);
-        nameView.setLayoutParams(
+        mTouchpadSelectionView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP);
+        mTouchpadSelectionView.setLayoutParams(
                 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
 
         mTouchpadVisualizationView = new TouchpadVisualizationView(context,
                 mTouchpadHardwareProperties);
-        mTouchpadVisualizationView.setBackgroundColor(Color.WHITE);
 
         mGestureInfoView = new TextView(context);
-        mGestureInfoView.setBackgroundColor(Color.BLACK);
         mGestureInfoView.setTextSize(TEXT_SIZE_SP);
         mGestureInfoView.setText("Latest Gesture: ");
         mGestureInfoView.setGravity(Gravity.CENTER);
-        mGestureInfoView.setTextColor(Color.WHITE);
         mGestureInfoView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP);
         mGestureInfoView.setLayoutParams(
                 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+        //TODO(b/369061237): Handle longer text
 
-        addView(nameView);
+        updateTheme(getResources().getConfiguration().uiMode);
+
+        addView(mTouchpadSelectionView);
         addView(mTouchpadVisualizationView);
         addView(mGestureInfoView);
 
@@ -239,6 +239,8 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
+
+        updateTheme(newConfig.uiMode);
         updateScreenDimensions();
         updateViewsDimensions();
 
@@ -250,6 +252,27 @@
         mWindowManager.updateViewLayout(this, mWindowLayoutParams);
     }
 
+    private void updateTheme(int uiMode) {
+        int currentNightMode = uiMode & Configuration.UI_MODE_NIGHT_MASK;
+        if (currentNightMode == Configuration.UI_MODE_NIGHT_YES) {
+            setNightModeTheme();
+        } else {
+            setLightModeTheme();
+        }
+    }
+
+    private void setLightModeTheme() {
+        mTouchpadVisualizationView.setLightModeTheme();
+        mGestureInfoView.setBackgroundColor(Color.WHITE);
+        mGestureInfoView.setTextColor(Color.BLACK);
+    }
+
+    private void setNightModeTheme() {
+        mTouchpadVisualizationView.setNightModeTheme();
+        mGestureInfoView.setBackgroundColor(Color.BLACK);
+        mGestureInfoView.setTextColor(Color.WHITE);
+    }
+
     private boolean isSlopExceeded(float deltaX, float deltaY) {
         return deltaX * deltaX + deltaY * deltaY >= mTouchSlop * mTouchSlop;
     }
@@ -333,12 +356,12 @@
 
     private void onTouchpadButtonPress() {
         Slog.d(TAG, "You clicked me!");
-        getChildAt(0).setBackgroundColor(Color.BLUE);
+        mTouchpadSelectionView.setBackgroundColor(BUTTON_PRESSED_BACKGROUND_COLOR);
     }
 
     private void onTouchpadButtonRelease() {
         Slog.d(TAG, "You released the click");
-        getChildAt(0).setBackgroundColor(Color.RED);
+        mTouchpadSelectionView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR);
     }
 
     /**
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
index cb43977..19c802b 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
@@ -45,8 +45,8 @@
     private boolean mTouchpadVisualizerEnabled = false;
 
     public TouchpadDebugViewController(Context context, Looper looper,
-            InputManagerService inputManagerService) {
-        //TODO(b/363979581): Handle multi-display scenarios
+                                       InputManagerService inputManagerService) {
+        //TODO(b/369059937): Handle multi-display scenarios
         mContext = context;
         mHandler = new Handler(looper);
         mInputManagerService = inputManagerService;
@@ -77,6 +77,14 @@
         }
     }
 
+    /**
+     * Switch to showing the touchpad with the given device ID
+     */
+    public void switchVisualisationToTouchpadId(int newDeviceId) {
+        if (mTouchpadDebugView != null) hideDebugView(mTouchpadDebugView.getTouchpadId());
+        showDebugView(newDeviceId);
+    }
+
     @Override
     public void onInputDeviceChanged(int deviceId) {
     }
@@ -117,7 +125,7 @@
                         touchpadId);
 
         mTouchpadDebugView = new TouchpadDebugView(mContext, touchpadId,
-                touchpadHardwareProperties);
+                touchpadHardwareProperties, this::switchVisualisationToTouchpadId);
         final WindowManager.LayoutParams mWindowLayoutParams =
                 mTouchpadDebugView.getWindowLayoutParams();
 
@@ -150,7 +158,7 @@
      * @param deviceId              the deviceId of the touchpad that is sending the hardware state
      */
     public void updateTouchpadHardwareState(TouchpadHardwareState touchpadHardwareState,
-            int deviceId) {
+                                            int deviceId) {
         if (mTouchpadDebugView != null) {
             mTouchpadDebugView.updateHardwareState(touchpadHardwareState, deviceId);
         }
diff --git a/services/core/java/com/android/server/input/debug/TouchpadSelectionView.java b/services/core/java/com/android/server/input/debug/TouchpadSelectionView.java
new file mode 100644
index 0000000..05217b6
--- /dev/null
+++ b/services/core/java/com/android/server/input/debug/TouchpadSelectionView.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 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.input.debug;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.hardware.input.InputManager;
+import android.view.Gravity;
+import android.view.InputDevice;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
+import android.widget.TextView;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+
+public class TouchpadSelectionView extends LinearLayout {
+    private static final float TEXT_SIZE_SP = 16.0f;
+
+    int mCurrentTouchpadId;
+
+    public TouchpadSelectionView(Context context, int touchpadId,
+                                 Consumer<Integer> touchpadSwitchHandler) {
+        super(context);
+        mCurrentTouchpadId = touchpadId;
+        init(context, touchpadSwitchHandler);
+    }
+
+    private void init(Context context, Consumer<Integer> touchpadSwitchHandler) {
+        setOrientation(HORIZONTAL);
+        setLayoutParams(new LayoutParams(
+                LayoutParams.WRAP_CONTENT,
+                LayoutParams.WRAP_CONTENT));
+        setBackgroundColor(Color.TRANSPARENT);
+
+        TextView nameView = new TextView(context);
+        nameView.setTextSize(TEXT_SIZE_SP);
+        nameView.setText(getTouchpadName(mCurrentTouchpadId));
+        nameView.setGravity(Gravity.LEFT);
+        nameView.setTextColor(Color.WHITE);
+
+        LayoutParams textParams = new LayoutParams(
+                LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+        textParams.rightMargin = 16;
+        nameView.setLayoutParams(textParams);
+
+        ImageButton arrowButton = new ImageButton(context);
+        arrowButton.setImageDrawable(context.getDrawable(android.R.drawable.arrow_down_float));
+        arrowButton.setForegroundGravity(Gravity.RIGHT);
+        arrowButton.setBackgroundColor(Color.TRANSPARENT);
+        arrowButton.setLayoutParams(new LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT));
+
+        arrowButton.setOnClickListener(v -> showPopupMenu(v, context, touchpadSwitchHandler));
+
+        addView(nameView);
+        addView(arrowButton);
+    }
+
+    private void showPopupMenu(View anchorView, Context context,
+                               Consumer<Integer> touchpadSwitchHandler) {
+        int i = 0;
+        PopupMenu popupMenu = new PopupMenu(context, anchorView);
+
+        final InputManager inputManager = Objects.requireNonNull(
+                mContext.getSystemService(InputManager.class));
+        for (int deviceId : inputManager.getInputDeviceIds()) {
+            InputDevice inputDevice = inputManager.getInputDevice(deviceId);
+            if (Objects.requireNonNull(inputDevice).supportsSource(
+                    InputDevice.SOURCE_TOUCHPAD | InputDevice.SOURCE_MOUSE)) {
+                popupMenu.getMenu().add(0, deviceId, i, getTouchpadName(deviceId));
+                i++;
+            }
+        }
+
+        popupMenu.setOnMenuItemClickListener(item -> {
+            if (item.getItemId() == mCurrentTouchpadId) {
+                return false;
+            }
+
+            touchpadSwitchHandler.accept(item.getItemId());
+            return true;
+        });
+
+        popupMenu.show();
+    }
+
+    private String getTouchpadName(int touchpadId) {
+        return Objects.requireNonNull(Objects.requireNonNull(
+                        mContext.getSystemService(InputManager.class))
+                .getInputDevice(touchpadId)).getName();
+    }
+}
diff --git a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
index 2eed9ba..eeec5cc 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.RectF;
 import android.util.Slog;
@@ -29,6 +30,7 @@
 
 import java.util.ArrayDeque;
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
 
 public class TouchpadVisualizationView extends View {
@@ -49,6 +51,7 @@
     private final Paint mOvalFillPaint;
     private final Paint mTracePaint;
     private final Paint mCenterPointPaint;
+    private final Paint mPressureTextPaint;
     private final RectF mTempOvalRect = new RectF();
 
     public TouchpadVisualizationView(Context context,
@@ -58,11 +61,9 @@
         mScaleFactor = 1;
         mOvalStrokePaint = new Paint();
         mOvalStrokePaint.setAntiAlias(true);
-        mOvalStrokePaint.setARGB(255, 0, 0, 0);
         mOvalStrokePaint.setStyle(Paint.Style.STROKE);
         mOvalFillPaint = new Paint();
         mOvalFillPaint.setAntiAlias(true);
-        mOvalFillPaint.setARGB(255, 0, 0, 0);
         mTracePaint = new Paint();
         mTracePaint.setAntiAlias(false);
         mTracePaint.setARGB(255, 0, 0, 255);
@@ -72,6 +73,8 @@
         mCenterPointPaint.setAntiAlias(true);
         mCenterPointPaint.setARGB(255, 255, 0, 0);
         mCenterPointPaint.setStrokeWidth(2);
+        mPressureTextPaint = new Paint();
+        mPressureTextPaint.setAntiAlias(true);
     }
 
     private void removeOldPoints() {
@@ -135,6 +138,13 @@
             mOvalFillPaint.setAlpha((int) pressureToOpacity);
 
             drawOval(canvas, newX, newY, newTouchMajor, newTouchMinor, newAngle);
+
+            String formattedPressure = String.format(Locale.getDefault(), "Ps: %.2f",
+                    touchpadFingerState.getPressure());
+            float textWidth = mPressureTextPaint.measureText(formattedPressure);
+
+            canvas.drawText(formattedPressure, newX - textWidth / 2,
+                    newY - newTouchMajor / 2, mPressureTextPaint);
         }
 
         mTempFingerStatesByTrackingId.clear();
@@ -195,6 +205,26 @@
         mScaleFactor = scaleFactor;
     }
 
+    /**
+     * Change the colors of the objects inside the view to light mode theme.
+     */
+    public void setLightModeTheme() {
+        this.setBackgroundColor(Color.rgb(20, 20, 20));
+        mPressureTextPaint.setARGB(255, 255, 255, 255);
+        mOvalFillPaint.setARGB(255, 255, 255, 255);
+        mOvalStrokePaint.setARGB(255, 255, 255, 255);
+    }
+
+    /**
+     * Change the colors of the objects inside the view to night mode theme.
+     */
+    public void setNightModeTheme() {
+        this.setBackgroundColor(Color.rgb(240, 240, 240));
+        mPressureTextPaint.setARGB(255, 0, 0, 0);
+        mOvalFillPaint.setARGB(255, 0, 0, 0);
+        mOvalStrokePaint.setARGB(255, 0, 0, 0);
+    }
+
     private float translateX(float x) {
         return translateRange(mTouchpadHardwareProperties.getLeft(),
                 mTouchpadHardwareProperties.getRight(), 0, getWidth(), x);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ba7d4d2..b15fcc9 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -33,6 +33,7 @@
 import static android.app.Notification.EXTRA_SUB_TEXT;
 import static android.app.Notification.EXTRA_TEXT;
 import static android.app.Notification.EXTRA_TEXT_LINES;
+import static android.app.Notification.EXTRA_TITLE;
 import static android.app.Notification.EXTRA_TITLE_BIG;
 import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
 import static android.app.Notification.FLAG_AUTO_CANCEL;
@@ -45,6 +46,7 @@
 import static android.app.Notification.FLAG_NO_DISMISS;
 import static android.app.Notification.FLAG_ONGOING_EVENT;
 import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
+import static android.app.Notification.FLAG_PROMOTED_ONGOING;
 import static android.app.Notification.FLAG_USER_INITIATED_JOB;
 import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
 import static android.app.NotificationChannel.NEWS_ID;
@@ -3516,7 +3518,7 @@
     private String getHistoryTitle(Notification n) {
         CharSequence title = null;
         if (n.extras != null) {
-            title = n.extras.getCharSequence(Notification.EXTRA_TITLE);
+            title = n.extras.getCharSequence(EXTRA_TITLE);
             if (title == null) {
                 title = n.extras.getCharSequence(EXTRA_TITLE_BIG);
             }
@@ -4114,6 +4116,75 @@
         }
 
         @Override
+        @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+        public boolean canBePromoted(String pkg, int uid) {
+            checkCallerIsSystemOrSystemUiOrShell();
+            if (!android.app.Flags.uiRichOngoing()) {
+                return false;
+            }
+            return mPreferencesHelper.canBePromoted(pkg, uid);
+        }
+
+        @Override
+        @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+        public void setCanBePromoted(String pkg, int uid, boolean promote) {
+            checkCallerIsSystemOrSystemUiOrShell();
+            if (!android.app.Flags.uiRichOngoing()) {
+                return;
+            }
+            boolean changed = mPreferencesHelper.setCanBePromoted(pkg, uid, promote);
+            if (changed) {
+                // check for pending/posted notifs from this app and update the flag
+                synchronized (mNotificationLock) {
+                    // for enqueued we just need to update the flag
+                    List<NotificationRecord> enqueued = findAppNotificationByListLocked(
+                            mEnqueuedNotifications, pkg, UserHandle.getUserId(uid));
+                    for (NotificationRecord r : enqueued) {
+                        if (promote
+                                && r.getNotification().hasPromotableCharacteristics()
+                                && r.getImportance() > IMPORTANCE_MIN) {
+                            r.getNotification().flags |= FLAG_PROMOTED_ONGOING;
+                        } else if (!promote) {
+                            r.getNotification().flags &= ~FLAG_PROMOTED_ONGOING;
+                        }
+                    }
+                    // if the notification is posted we need to update the flag and tell listeners
+                    List<NotificationRecord> posted = findAppNotificationByListLocked(
+                            mNotificationList, pkg, UserHandle.getUserId(uid));
+                    for (NotificationRecord r : posted) {
+                        if (promote
+                                && !hasFlag(r.getNotification().flags, FLAG_PROMOTED_ONGOING)
+                                && r.getNotification().hasPromotableCharacteristics()
+                                && r.getImportance() > IMPORTANCE_MIN) {
+                            r.getNotification().flags |= FLAG_PROMOTED_ONGOING;
+                            // we could set a wake lock here but this value should only change
+                            // in response to user action, so the device should be awake long enough
+                            // to post
+                            PostNotificationTracker tracker =
+                                    mPostNotificationTrackerFactory.newTracker(null);
+                            // Set false for isAppForeground because that field is only used
+                            // for bubbles and messagingstyle can not be promoted
+                            mHandler.post(new EnqueueNotificationRunnable(
+                                    r.getUser().getIdentifier(),
+                                    r, /* isAppForeground */ false, /* isAppProvided= */ false,
+                                    tracker));
+                        } else if (!promote
+                                && hasFlag(r.getNotification().flags, FLAG_PROMOTED_ONGOING)){
+                            r.getNotification().flags &= ~FLAG_PROMOTED_ONGOING;
+                            PostNotificationTracker tracker =
+                                    mPostNotificationTrackerFactory.newTracker(null);
+                            mHandler.post(new EnqueueNotificationRunnable(
+                                    r.getUser().getIdentifier(),
+                                    r, /* isAppForeground */ false, /* isAppProvided= */ false,
+                                    tracker));
+                        }
+                    }
+                }
+                handleSavePolicyFile();
+            }
+        }
+
+        @Override
         public boolean hasSentValidMsg(String pkg, int uid) {
             checkCallerIsSystem();
             return mPreferencesHelper.hasSentValidMsg(pkg, uid);
@@ -7698,6 +7769,16 @@
             return false;
         }
 
+        if (android.app.Flags.uiRichOngoing()) {
+            // This would normally be done in fixNotification(), but we need the channel info so
+            // it's done a little late
+            if (mPreferencesHelper.canBePromoted(pkg, notificationUid)
+                    && notification.hasPromotableCharacteristics()
+                    && channel.getImportance() > IMPORTANCE_MIN) {
+                notification.flags |= FLAG_PROMOTED_ONGOING;
+            }
+        }
+
         final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
         r.setIsAppImportanceLocked(mPermissionHelper.isPermissionUserSet(pkg, userId));
         r.setPostSilently(postSilently);
@@ -7938,6 +8019,9 @@
             }
         }
 
+        // Apps cannot set this flag
+         notification.flags &= ~FLAG_PROMOTED_ONGOING;
+
         // Ensure CallStyle has all the correct actions
         if (notification.isStyle(Notification.CallStyle.class)) {
             Notification.Builder builder =
@@ -8061,12 +8145,7 @@
 
     private void checkRemoteViews(String pkg, String tag, int id, Notification notification) {
         if (android.app.Flags.removeRemoteViews()) {
-            if (notification.contentView != null || notification.bigContentView != null
-                    ||  notification.headsUpContentView != null
-                    || (notification.publicVersion != null
-                    && (notification.publicVersion.contentView != null
-                    || notification.publicVersion.bigContentView != null
-                    || notification.publicVersion.headsUpContentView != null))) {
+            if (notification.containsCustomViews()) {
                 Slog.i(TAG, "Removed customViews for " + pkg);
                 mUsageStats.registerImageRemoved(pkg);
             }
@@ -9236,8 +9315,8 @@
             }
         }
 
-        final String oldTitle = String.valueOf(oldN.extras.get(Notification.EXTRA_TITLE));
-        final String newTitle = String.valueOf(newN.extras.get(Notification.EXTRA_TITLE));
+        final String oldTitle = String.valueOf(oldN.extras.get(EXTRA_TITLE));
+        final String newTitle = String.valueOf(newN.extras.get(EXTRA_TITLE));
         if (!Objects.equals(oldTitle, newTitle)) {
             if (DEBUG_INTERRUPTIVENESS) {
                 Slog.v(TAG, "INTERRUPTIVENESS: "
@@ -10654,6 +10733,22 @@
     }
 
     @GuardedBy("mNotificationLock")
+    @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    private @NonNull List<NotificationRecord> findAppNotificationByListLocked(
+            ArrayList<NotificationRecord> list, String pkg, int userId) {
+        List<NotificationRecord> records = new ArrayList<>();
+        final int len = list.size();
+        for (int i = 0; i < len; i++) {
+            NotificationRecord r = list.get(i);
+            if (notificationMatchesUserId(r, userId, false)
+                    && r.getSbn().getPackageName().equals(pkg)) {
+                records.add(r);
+            }
+        }
+        return records;
+    }
+
+    @GuardedBy("mNotificationLock")
     private @NonNull List<NotificationRecord> findGroupNotificationByListLocked(
             ArrayList<NotificationRecord> list, String pkg, String groupKey, int userId) {
         List<NotificationRecord> records = new ArrayList<>();
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index a4fdb75..fcc8d2f 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -41,6 +41,7 @@
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -162,6 +163,7 @@
     private static final String ATT_SENT_VALID_MESSAGE = "sent_valid_msg";
     private static final String ATT_USER_DEMOTED_INVALID_MSG_APP = "user_demote_msg_app";
     private static final String ATT_SENT_VALID_BUBBLE = "sent_valid_bubble";
+    private static final String ATT_PROMOTE_NOTIFS = "promote";
 
     private static final String ATT_CREATION_TIME = "creation_time";
 
@@ -351,6 +353,10 @@
             r.userDemotedMsgApp = parser.getAttributeBoolean(
                     null, ATT_USER_DEMOTED_INVALID_MSG_APP, false);
             r.hasSentValidBubble = parser.getAttributeBoolean(null, ATT_SENT_VALID_BUBBLE, false);
+            if (android.app.Flags.uiRichOngoing()) {
+                r.canHavePromotedNotifs =
+                        parser.getAttributeBoolean(null, ATT_PROMOTE_NOTIFS, false);
+            }
 
             final int innerDepth = parser.getDepth();
             int type;
@@ -739,6 +745,11 @@
         out.attributeBoolean(null, ATT_USER_DEMOTED_INVALID_MSG_APP,
                 r.userDemotedMsgApp);
         out.attributeBoolean(null, ATT_SENT_VALID_BUBBLE, r.hasSentValidBubble);
+        if (android.app.Flags.uiRichOngoing()) {
+            if (r.canHavePromotedNotifs) {
+                out.attributeBoolean(null, ATT_PROMOTE_NOTIFS, r.canHavePromotedNotifs);
+            }
+        }
 
         if (Flags.persistIncompleteRestoreData() && r.uid == UNKNOWN_UID) {
             out.attributeLong(null, ATT_CREATION_TIME, r.creationTime);
@@ -839,6 +850,28 @@
         }
     }
 
+    @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    public boolean canBePromoted(String packageName, int uid) {
+        synchronized (mLock) {
+            return getOrCreatePackagePreferencesLocked(packageName, uid).canHavePromotedNotifs;
+        }
+    }
+
+    @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    public boolean setCanBePromoted(String packageName, int uid, boolean promote) {
+        boolean changed = false;
+        synchronized (mLock) {
+            PackagePreferences pkgPrefs = getOrCreatePackagePreferencesLocked(packageName, uid);
+            if (pkgPrefs.canHavePromotedNotifs != promote) {
+                pkgPrefs.canHavePromotedNotifs = promote;
+                changed = true;
+            }
+        }
+        // no need to send a ranking update because we need to update the flag value on all pending
+        // and posted notifs and NMS will take care of that
+        return changed;
+    }
+
     public boolean isInInvalidMsgState(String packageName, int uid) {
         synchronized (mLock) {
             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
@@ -2180,6 +2213,10 @@
                     pw.print(" fixedImportance=");
                     pw.print(r.fixedImportance);
                 }
+                if (android.app.Flags.uiRichOngoing() && r.canHavePromotedNotifs) {
+                    pw.print(" promoted=");
+                    pw.print(r.canHavePromotedNotifs);
+                }
                 pw.println();
                 for (NotificationChannel channel : r.channels.values()) {
                     pw.print(prefix);
@@ -3028,6 +3065,9 @@
         boolean migrateToPm = false;
         long creationTime;
 
+        @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+        boolean canHavePromotedNotifs = false;
+
         @UserIdInt int userId;
 
         Delegate delegate = null;
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index e9db1b5..626c3dd 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1358,7 +1358,8 @@
             if (isNew) {
                 // Newly created rule with no provided policy; fill in with the default.
                 zenRule.zenPolicy =
-                        Flags.modesUi() ? mDefaultConfig.getZenPolicy() : mConfig.getZenPolicy();
+                        (Flags.modesUi() ? mDefaultConfig.getZenPolicy() : mConfig.getZenPolicy())
+                                .copy();
                 return true;
             }
             // Otherwise, a null policy means no policy changes, so we can stop here.
@@ -1721,7 +1722,7 @@
             // booleans to determine whether to reset the rules to the default rules
             boolean allRulesDisabled = true;
             boolean hasDefaultRules = config.automaticRules.containsAll(
-                    ZenModeConfig.DEFAULT_RULE_IDS);
+                    ZenModeConfig.getDefaultRuleIds());
 
             long time = Flags.modesApi() ? mClock.millis() : System.currentTimeMillis();
             if (config.automaticRules != null && config.automaticRules.size() > 0) {
@@ -1773,7 +1774,7 @@
                 // definition cannot have a rule with TYPE_BEDTIME (or any other type).
                 config.automaticRules = new ArrayMap<>();
                 for (ZenRule rule : mDefaultConfig.automaticRules.values()) {
-                    config.automaticRules.put(rule.id, rule);
+                    config.automaticRules.put(rule.id, rule.copy());
                 }
                 reason += ", reset to default rules";
             }
@@ -1798,6 +1799,14 @@
                 config.deletedRules.clear();
             }
 
+            if (Flags.modesUi() && config.automaticRules != null) {
+                ZenRule obsoleteEventsRule = config.automaticRules.get(
+                        ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
+                if (obsoleteEventsRule != null && !obsoleteEventsRule.enabled) {
+                    config.automaticRules.remove(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
+                }
+            }
+
             if (DEBUG) Log.d(TAG, reason);
             synchronized (mConfigLock) {
                 setConfigLocked(config, null,
@@ -2256,7 +2265,7 @@
     private static void updateRuleStringsForCurrentLocale(Context context,
             ZenModeConfig defaultConfig) {
         for (ZenRule rule : defaultConfig.automaticRules.values()) {
-            if (ZenModeConfig.EVENTS_DEFAULT_RULE_ID.equals(rule.id)) {
+            if (ZenModeConfig.EVENTS_OBSOLETE_RULE_ID.equals(rule.id)) {
                 rule.name = context.getResources()
                         .getString(R.string.zen_mode_default_events_name);
             } else if (ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID.equals(rule.id)) {
@@ -2278,7 +2287,7 @@
         }
         ZenPolicy defaultPolicy = defaultConfig.getZenPolicy();
         for (ZenRule rule : defaultConfig.automaticRules.values()) {
-            if (ZenModeConfig.DEFAULT_RULE_IDS.contains(rule.id) && rule.zenPolicy == null) {
+            if (ZenModeConfig.getDefaultRuleIds().contains(rule.id) && rule.zenPolicy == null) {
                 rule.zenPolicy = defaultPolicy.copy();
             }
         }
@@ -2482,7 +2491,7 @@
             List<StatsEvent> events) {
         // Make the ID safe.
         String id = rule.id == null ? "" : rule.id;
-        if (!ZenModeConfig.DEFAULT_RULE_IDS.contains(id)) {
+        if (!ZenModeConfig.getDefaultRuleIds().contains(id)) {
             id = "";
         }
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 3e70d92..8bab9de 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1101,8 +1101,6 @@
         if (android.multiuser.Flags.cachesNotInvalidatedAtStartReadOnly()) {
             UserManager.invalidateIsUserUnlockedCache();
             UserManager.invalidateQuietModeEnabledCache();
-            UserManager.invalidateStaticUserProperties();
-            UserManager.invalidateUserPropertiesCache();
             UserManager.invalidateUserSerialNumberCache();
         }
     }
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 46207c1..b43ddaa 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -572,7 +572,7 @@
                 return false;
             }
 
-            // First check if the user started on display
+            // First check if the user is assigned to a display
             int userAssignedToDisplay = getUserStartedOnDisplay(displayId);
             if (userAssignedToDisplay != USER_NULL) {
                 Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): failed because display was assigned"
@@ -918,10 +918,16 @@
                 if (!isStartedVisibleProfileLocked(userId)) {
                     return userId;
                 } else if (DBG) {
-                    Slogf.d(TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's "
-                            + "a profile", displayId, userId);
+                    Slogf.d(TAG,
+                            "getUserAssignedToDisplay(%d): skipping user %d because it's a profile",
+                            displayId, userId);
                 }
             }
+            int userAssignedToExtraDisplay = mExtraDisplaysAssignedToUsers.get(displayId,
+                    USER_NULL);
+            if (userAssignedToExtraDisplay != USER_NULL) {
+                return userAssignedToExtraDisplay;
+            }
         }
         if (!returnCurrentUserByDefault) {
             if (DBG) {
diff --git a/services/core/java/com/android/server/power/PowerManagerShellCommand.java b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
index 20184e9f..f69a017 100644
--- a/services/core/java/com/android/server/power/PowerManagerShellCommand.java
+++ b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
@@ -16,13 +16,19 @@
 
 package com.android.server.power;
 
+import android.app.AlarmManager;
+import android.app.IAlarmCompleteListener;
+import android.app.IAlarmListener;
+import android.app.IAlarmManager;
 import android.content.Context;
 import android.content.Intent;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.PowerManagerInternal;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.ShellCommand;
+import android.os.SystemClock;
 import android.util.SparseArray;
 import android.view.Display;
 
@@ -34,12 +40,26 @@
 
     private final Context mContext;
     private final PowerManagerService.BinderService mService;
+    private final IAlarmListener mAlarmListener;
+    private IAlarmManager mAlarmManager;
 
     private SparseArray<WakeLock> mProxWakelocks = new SparseArray<>();
 
     PowerManagerShellCommand(Context context, PowerManagerService.BinderService service) {
         mContext = context;
         mService = service;
+        mAlarmManager =
+            IAlarmManager.Stub.asInterface(ServiceManager.getService(Context.ALARM_SERVICE));
+        mAlarmListener = new IAlarmListener.Stub() {
+            @Override
+            public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
+                mService.wakeUp(
+                        SystemClock.uptimeMillis(),
+                        PowerManager.WAKE_REASON_APPLICATION,
+                        "PowerManagerShellCommand",
+                        mContext.getOpPackageName());
+            }
+        };
     }
 
     @Override
@@ -65,6 +85,10 @@
                     return runSetProx();
                 case "set-face-down-detector":
                     return runSetFaceDownDetector();
+                case "sleep":
+                    return runSleep();
+                case "wakeup":
+                    return runWakeUp();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -194,6 +218,70 @@
         return 0;
     }
 
+    private int runSleep() {
+        try {
+            mService.goToSleep(
+                    SystemClock.uptimeMillis(),
+                    PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+                    PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
+        } catch (Exception e) {
+            final PrintWriter pw = getOutPrintWriter();
+            pw.println("Error: " + e);
+            return -1;
+        }
+        return 0;
+    }
+
+    private int runWakeUp() {
+        final PrintWriter pw = getOutPrintWriter();
+        String delay = getNextArg();
+        if (delay == null) {
+            try {
+                mService.wakeUp(
+                        SystemClock.uptimeMillis(),
+                        PowerManager.WAKE_REASON_APPLICATION,
+                        "PowerManagerShellCommand",
+                        mContext.getOpPackageName());
+            } catch (Exception e) {
+                pw.println("Error: " + e);
+                return -1;
+            }
+        } else {
+            long delayMillis;
+            try {
+                delayMillis = Long.parseLong(delay);
+            } catch (NumberFormatException e) {
+                pw.println("Error: Can't parse arg " + delay + " as a long: " + e);
+                return -1;
+            }
+            if (delayMillis < 0) {
+                pw.println("Error: Can't set a negative delay: " + delayMillis);
+                return -1;
+            }
+            long wakeUpTime = System.currentTimeMillis() + delayMillis;
+            if (mAlarmManager == null) {
+                // PowerManagerShellCommand may be initialized before AlarmManagerService
+                // is brought up. Make sure mAlarmManager exists.
+                mAlarmManager = IAlarmManager.Stub.asInterface(
+                        ServiceManager.getService(Context.ALARM_SERVICE));
+            }
+            try {
+                // This command is called by the shell, which has "com.android.shell" as package
+                // name.
+                pw.println("Schedule an alarm to wakeup in "
+                        + delayMillis + " ms, on behalf of shell.");
+                mAlarmManager.set("com.android.shell",
+                        AlarmManager.RTC_WAKEUP, wakeUpTime,
+                        0, 0, AlarmManager.FLAG_PRIORITIZE,
+                        null, mAlarmListener, "PowerManagerShellCommand", null, null);
+            } catch (Exception e) {
+                pw.println("Error: " + e);
+                return -1;
+            }
+        }
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         final PrintWriter pw = getOutPrintWriter();
@@ -221,6 +309,11 @@
         pw.println("    created by set-prox including their held status.");
         pw.println("  set-face-down-detector [true|false]");
         pw.println("    sets whether we use face down detector timeouts or not");
+        pw.println("  sleep");
+        pw.println("    requests to sleep the device");
+        pw.println("  wakeup <delay>");
+        pw.println("    requests to wake up the device. If a delay of milliseconds is specified,");
+        pw.println("    alarm manager will schedule a wake up after the delay.");
 
         pw.println();
         Intent.printIntentArgsHelp(pw , "");
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index e3d71e4..f78c448 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -81,7 +81,7 @@
 public final class RollbackPackageHealthObserver implements PackageHealthObserver {
     private static final String TAG = "RollbackPackageHealthObserver";
     private static final String NAME = "rollback-observer";
-    private static final String ACTION_NAME = RollbackPackageHealthObserver.class.getName();
+    private static final String CLASS_NAME = RollbackPackageHealthObserver.class.getName();
 
     private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
             | ApplicationInfo.FLAG_SYSTEM;
@@ -610,14 +610,16 @@
                 }
             };
 
+            String intentActionName = CLASS_NAME + rollback.getRollbackId();
             // Register the BroadcastReceiver
             mContext.registerReceiver(rollbackReceiver,
-                    new IntentFilter(ACTION_NAME),
+                    new IntentFilter(intentActionName),
                     Context.RECEIVER_NOT_EXPORTED);
 
-            Intent intentReceiver = new Intent(ACTION_NAME);
+            Intent intentReceiver = new Intent(intentActionName);
             intentReceiver.putExtra("rollbackId", rollback.getRollbackId());
             intentReceiver.setPackage(mContext.getPackageName());
+            intentReceiver.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
 
             PendingIntent rollbackPendingIntent = PendingIntent.getBroadcast(mContext,
                     rollback.getRollbackId(),
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ccc9b17..12d733f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2641,9 +2641,15 @@
             return true;
         }
         // Only do transfer after transaction has done when starting window exist.
-        if (mStartingData != null && mStartingData.mWaitForSyncTransactionCommit) {
-            mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT;
-            return true;
+        if (mStartingData != null) {
+            final boolean isWaitingForSyncTransactionCommit =
+                    Flags.removeStartingWindowWaitForMultiTransitions()
+                            ? getSyncTransactionCommitCallbackDepth() > 0
+                            : mStartingData.mWaitForSyncTransactionCommit;
+            if (isWaitingForSyncTransactionCommit) {
+                mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT;
+                return true;
+            }
         }
         requestCopySplashScreen();
         return isTransferringSplashScreen();
@@ -2847,7 +2853,11 @@
         final boolean animate;
         final boolean hasImeSurface;
         if (mStartingData != null) {
-            if (mStartingData.mWaitForSyncTransactionCommit
+            final boolean isWaitingForSyncTransactionCommit =
+                    Flags.removeStartingWindowWaitForMultiTransitions()
+                            ? getSyncTransactionCommitCallbackDepth() > 0
+                            : mStartingData.mWaitForSyncTransactionCommit;
+            if (isWaitingForSyncTransactionCommit
                     || mSyncState != SYNC_STATE_NONE) {
                 mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_REMOVE_DIRECTLY;
                 mStartingData.mPrepareRemoveAnimation = prepareAnimation;
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index f0a4763..57b8792 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -75,7 +75,8 @@
 
     private final Rect mTmpRect = new Rect();
     private final InsetsSourceControl mFakeControl;
-    private final Consumer<Transaction> mSetLeashPositionConsumer;
+    private final Point mPosition = new Point();
+    private final Consumer<Transaction> mSetControlPositionConsumer;
     private @Nullable InsetsControlTarget mPendingControlTarget;
     private @Nullable InsetsControlTarget mFakeControlTarget;
 
@@ -126,13 +127,14 @@
                 source.getId(), source.getType(), null /* leash */, false /* initialVisible */,
                 new Point(), Insets.NONE);
         mControllable = (InsetsPolicy.CONTROLLABLE_TYPES & source.getType()) != 0;
-        mSetLeashPositionConsumer = t -> {
-            if (mControl != null) {
-                final SurfaceControl leash = mControl.getLeash();
-                if (leash != null) {
-                    final Point position = mControl.getSurfacePosition();
-                    t.setPosition(leash, position.x, position.y);
-                }
+        mSetControlPositionConsumer = t -> {
+            if (mControl == null || mControlTarget == null) {
+                return;
+            }
+            boolean changed = mControl.setSurfacePosition(mPosition.x, mPosition.y);
+            final SurfaceControl leash = mControl.getLeash();
+            if (changed && leash != null) {
+                t.setPosition(leash, mPosition.x, mPosition.y);
             }
             if (mHasPendingPosition) {
                 mHasPendingPosition = false;
@@ -140,9 +142,22 @@
                     mStateController.notifyControlTargetChanged(mPendingControlTarget, this);
                 }
             }
+            changed |= updateInsetsHint();
+            if (changed) {
+                mStateController.notifyControlChanged(mControlTarget, this);
+            }
         };
     }
 
+    private boolean updateInsetsHint() {
+        final Insets insetsHint = getInsetsHint();
+        if (!mControl.getInsetsHint().equals(insetsHint)) {
+            mControl.setInsetsHint(insetsHint);
+            return true;
+        }
+        return false;
+    }
+
     InsetsSource getSource() {
         return mSource;
     }
@@ -363,26 +378,32 @@
         }
         final boolean serverVisibleChanged = mServerVisible != isServerVisible;
         setServerVisible(isServerVisible);
-        updateInsetsControlPosition(windowState, serverVisibleChanged);
-    }
-
-    void updateInsetsControlPosition(WindowState windowState) {
-        updateInsetsControlPosition(windowState, false);
-    }
-
-    private void updateInsetsControlPosition(WindowState windowState,
-            boolean serverVisibleChanged) {
-        if (mControl == null) {
-            return;
+        final boolean positionChanged = updateInsetsControlPosition(windowState);
+        if (mControl != null && !positionChanged
+                // The insets hint would be updated if the position is changed. Here updates it for
+                // the possible change of the bounds or the server visibility.
+                && (updateInsetsHint()
+                        || serverVisibleChanged
+                                && android.view.inputmethod.Flags.refactorInsetsController())) {
+            // Only call notifyControlChanged here when the position is not changed. Otherwise, it
+            // is called or is scheduled to be called during updateInsetsControlPosition.
+            mStateController.notifyControlChanged(mControlTarget, this);
         }
-        boolean changed = false;
+    }
+
+    /**
+     * @return {#code true} if the surface position of the control is changed.
+     */
+    boolean updateInsetsControlPosition(WindowState windowState) {
+        if (mControl == null) {
+            return false;
+        }
         final Point position = getWindowFrameSurfacePosition();
-        if (mControl.setSurfacePosition(position.x, position.y) && mControlTarget != null) {
-            changed = true;
+        if (!mPosition.equals(position)) {
+            mPosition.set(position.x, position.y);
             if (windowState != null && windowState.getWindowFrames().didFrameSizeChange()
                     && windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) {
-                mHasPendingPosition = true;
-                windowState.applyWithNextDraw(mSetLeashPositionConsumer);
+                windowState.applyWithNextDraw(mSetControlPositionConsumer);
             } else {
                 Transaction t = mWindowContainer.getSyncTransaction();
                 if (windowState != null) {
@@ -399,20 +420,11 @@
                         }
                     }
                 }
-                mSetLeashPositionConsumer.accept(t);
+                mSetControlPositionConsumer.accept(t);
             }
+            return true;
         }
-        final Insets insetsHint = getInsetsHint();
-        if (!mControl.getInsetsHint().equals(insetsHint)) {
-            mControl.setInsetsHint(insetsHint);
-            changed = true;
-        }
-        if (android.view.inputmethod.Flags.refactorInsetsController() && serverVisibleChanged) {
-            changed = true;
-        }
-        if (changed) {
-            mStateController.notifyControlChanged(mControlTarget, this);
-        }
+        return false;
     }
 
     private Point getWindowFrameSurfacePosition() {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 5550f3e..d295378 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -699,8 +699,10 @@
             final WindowState win = mService.windowForClientLocked(this, window,
                     false /* throwOnError */);
             if (win != null) {
-                ImeTracker.forLogging().onProgress(imeStatsToken,
-                        ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
+                if (android.view.inputmethod.Flags.refactorInsetsController()) {
+                    ImeTracker.forLogging().onProgress(imeStatsToken,
+                            ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
+                }
                 win.setRequestedVisibleTypes(requestedVisibleTypes);
                 win.getDisplayContent().getInsetsPolicy().onRequestedVisibleTypesChanged(win,
                         imeStatsToken);
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index 24fb207..896612d 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -68,7 +68,9 @@
      * window.
      * Note this isn't equal to transition playing, the period should be
      * Sync finishNow -> Start transaction apply.
+     * @deprecated TODO(b/362347290): cleanup after fix ramp up
      */
+    @Deprecated
     boolean mWaitForSyncTransactionCommit;
 
     /**
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a2fda0a..86bb75a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -35,6 +35,8 @@
 import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
 import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
+import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
+import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
@@ -49,6 +51,7 @@
 import static android.view.SurfaceControl.METADATA_TASK_ID;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
@@ -133,6 +136,7 @@
 import android.app.PictureInPictureParams;
 import android.app.TaskInfo;
 import android.app.WindowConfiguration;
+import android.app.compat.CompatChanges;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -503,6 +507,12 @@
     int mOffsetXForInsets;
     int mOffsetYForInsets;
 
+    /**
+     * Whether the compatibility overrides that change the resizability of the app should be allowed
+     * for the specific app.
+     */
+    boolean mAllowForceResizeOverride = true;
+
     private final AnimatingActivityRegistry mAnimatingActivityRegistry =
             new AnimatingActivityRegistry();
 
@@ -666,6 +676,7 @@
             intent = _intent;
             mMinWidth = minWidth;
             mMinHeight = minHeight;
+            updateAllowForceResizeOverride();
         }
         mAtmService.getTaskChangeNotificationController().notifyTaskCreated(_taskId, realActivity);
         mHandler = new ActivityTaskHandler(mTaskSupervisor.mLooper);
@@ -1028,6 +1039,7 @@
             mTaskSupervisor.mRecentTasks.remove(this);
             mTaskSupervisor.mRecentTasks.add(this);
         }
+        updateAllowForceResizeOverride();
     }
 
     /** Sets the original minimal width and height. */
@@ -1823,6 +1835,17 @@
                 -1 /* don't check PID */, -1 /* don't check UID */, this);
     }
 
+    private void updateAllowForceResizeOverride() {
+        try {
+            mAllowForceResizeOverride = mAtmService.mContext.getPackageManager().getProperty(
+                    PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES,
+                    getBasePackageName()).getBoolean();
+        } catch (PackageManager.NameNotFoundException e) {
+            // Package not found or property not defined, reset to default value.
+            mAllowForceResizeOverride = true;
+        }
+    }
+
     /**
      * Check that a given bounds matches the application requested orientation.
      *
@@ -2812,7 +2835,18 @@
     boolean isResizeable(boolean checkPictureInPictureSupport) {
         final boolean forceResizable = mAtmService.mForceResizableActivities
                 && getActivityType() == ACTIVITY_TYPE_STANDARD;
-        return forceResizable || ActivityInfo.isResizeableMode(mResizeMode)
+        if (forceResizable) return true;
+
+        final UserHandle userHandle = UserHandle.getUserHandleForUid(mUserId);
+        final boolean forceResizableOverride = mAllowForceResizeOverride
+                && CompatChanges.isChangeEnabled(
+                        FORCE_RESIZE_APP, getBasePackageName(), userHandle);
+        final boolean forceNonResizableOverride = mAllowForceResizeOverride
+                && CompatChanges.isChangeEnabled(
+                        FORCE_NON_RESIZE_APP, getBasePackageName(), userHandle);
+
+        if (forceNonResizableOverride) return false;
+        return forceResizableOverride || ActivityInfo.isResizeableMode(mResizeMode)
                 || (mSupportsPictureInPicture && checkPictureInPictureSupport);
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 92953e5..83e714d 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -429,7 +429,7 @@
             }
 
             final IBinder activityToken;
-            if (activity.getPid() == mOrganizerPid) {
+            if (activity.getPid() == mOrganizerPid && activity.getUid() == mOrganizerUid) {
                 // We only pass the actual token if the activity belongs to the organizer process.
                 activityToken = activity.token;
             } else {
@@ -458,7 +458,8 @@
                 change.setTaskFragmentToken(lastParentTfToken);
             }
             // Only pass the activity token to the client if it belongs to the same process.
-            if (nextFillTaskActivity != null && nextFillTaskActivity.getPid() == mOrganizerPid) {
+            if (nextFillTaskActivity != null && nextFillTaskActivity.getPid() == mOrganizerPid
+                    && nextFillTaskActivity.getUid() == mOrganizerUid) {
                 change.setOtherActivityToken(nextFillTaskActivity.token);
             }
             return change;
@@ -553,6 +554,10 @@
                         "Replacing existing organizer currently unsupported");
             }
 
+            if (pid <= 0) {
+                throw new IllegalStateException("Cannot register from invalid pid: " + pid);
+            }
+
             if (restoreFromCachedStateIfPossible(organizer, pid, uid, outSavedState)) {
                 return;
             }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 0a9cb1c..1c03ba5 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -4351,4 +4351,7 @@
         t.merge(mSyncTransaction);
     }
 
+    int getSyncTransactionCommitCallbackDepth() {
+        return mSyncTransactionCommitCallbackDepth;
+    }
 }
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index d2493c5..5cd117b 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -357,6 +357,7 @@
     FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId);
     void setStylusPointerIconEnabled(bool enabled);
     void setInputMethodConnectionIsActive(bool isActive);
+    void setKeyRemapping(const std::map<int32_t, int32_t>& keyRemapping);
 
     /* --- InputReaderPolicyInterface implementation --- */
 
@@ -504,6 +505,9 @@
 
         // True if there is an active input method connection.
         bool isInputMethodConnectionActive{false};
+
+        // Keycodes to be remapped.
+        std::map<int32_t /* fromKeyCode */, int32_t /* toKeyCode */> keyRemapping{};
     } mLocked GUARDED_BY(mLock);
 
     std::atomic<bool> mInteractive;
@@ -761,6 +765,8 @@
         outConfig->stylusButtonMotionEventsEnabled = mLocked.stylusButtonMotionEventsEnabled;
 
         outConfig->stylusPointerIconEnabled = mLocked.stylusPointerIconEnabled;
+
+        outConfig->keyRemapping = mLocked.keyRemapping;
     } // release lock
 }
 
@@ -1910,6 +1916,16 @@
     mInputManager->getDispatcher().setInputMethodConnectionIsActive(isActive);
 }
 
+void NativeInputManager::setKeyRemapping(const std::map<int32_t, int32_t>& keyRemapping) {
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+        mLocked.keyRemapping = keyRemapping;
+    } // release lock
+
+    mInputManager->getReader().requestRefreshConfiguration(
+            InputReaderConfiguration::Change::KEY_REMAPPING);
+}
+
 // ----------------------------------------------------------------------------
 
 static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) {
@@ -1983,10 +1999,19 @@
     return vec;
 }
 
-static void nativeAddKeyRemapping(JNIEnv* env, jobject nativeImplObj, jint deviceId,
-                                  jint fromKeyCode, jint toKeyCode) {
+static void nativeSetKeyRemapping(JNIEnv* env, jobject nativeImplObj, jintArray fromKeyCodesArr,
+                                  jintArray toKeyCodesArr) {
+    const std::vector<int32_t> fromKeycodes = getIntArray(env, fromKeyCodesArr);
+    const std::vector<int32_t> toKeycodes = getIntArray(env, toKeyCodesArr);
+    if (fromKeycodes.size() != toKeycodes.size()) {
+        jniThrowRuntimeException(env, "FromKeycodes and toKeycodes cannot match.");
+    }
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
-    im->getInputManager()->getReader().addKeyRemapping(deviceId, fromKeyCode, toKeyCode);
+    std::map<int32_t, int32_t> keyRemapping;
+    for (int i = 0; i < fromKeycodes.size(); i++) {
+        keyRemapping.insert_or_assign(fromKeycodes[i], toKeycodes[i]);
+    }
+    im->setKeyRemapping(keyRemapping);
 }
 
 static jboolean nativeHasKeys(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint sourceMask,
@@ -2491,7 +2516,7 @@
             jTypeId = env->GetStaticIntField(gLightClassInfo.clazz,
                                              gLightClassInfo.lightTypeKeyboardMicMute);
         } else {
-            ALOGW("Unknown light type %d", lightInfo.type);
+            ALOGW("Unknown light type %s", ftl::enum_string(lightInfo.type).c_str());
             continue;
         }
 
@@ -2955,7 +2980,7 @@
         {"getScanCodeState", "(III)I", (void*)nativeGetScanCodeState},
         {"getKeyCodeState", "(III)I", (void*)nativeGetKeyCodeState},
         {"getSwitchState", "(III)I", (void*)nativeGetSwitchState},
-        {"addKeyRemapping", "(III)V", (void*)nativeAddKeyRemapping},
+        {"setKeyRemapping", "([I[I)V", (void*)nativeSetKeyRemapping},
         {"hasKeys", "(II[I[Z)Z", (void*)nativeHasKeys},
         {"getKeyCodeForKeyLocation", "(II)I", (void*)nativeGetKeyCodeForKeyLocation},
         {"createInputChannel", "(Ljava/lang/String;)Landroid/view/InputChannel;",
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
index c05c381..bc64e15 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
@@ -36,7 +36,6 @@
 import com.android.internal.infra.AndroidFuture
 import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults
 import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.MoreExecutors
 import java.util.concurrent.atomic.AtomicBoolean
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -46,7 +45,6 @@
 class MetadataSyncAdapterTest {
     private val context = InstrumentationRegistry.getInstrumentation().targetContext
     private val appSearchManager = context.getSystemService(AppSearchManager::class.java)
-    private val testExecutor = MoreExecutors.directExecutor()
     private val packageManager = context.packageManager
 
     @Test
@@ -138,8 +136,7 @@
             PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
         runtimeSearchSession.put(putDocumentsRequest).get()
         staticSearchSession.put(putDocumentsRequest).get()
-        val metadataSyncAdapter =
-            MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+        val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
 
         val submitSyncRequest =
             metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
@@ -180,8 +177,7 @@
         val putDocumentsRequest: PutDocumentsRequest =
             PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
         staticSearchSession.put(putDocumentsRequest).get()
-        val metadataSyncAdapter =
-            MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+        val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
 
         val submitSyncRequest =
             metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
@@ -236,8 +232,7 @@
         val putDocumentsRequest: PutDocumentsRequest =
             PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
         runtimeSearchSession.put(putDocumentsRequest).get()
-        val metadataSyncAdapter =
-            MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+        val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
 
         val submitSyncRequest =
             metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index 3dd2f24a..1cad255 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -509,8 +509,12 @@
 
         mAppStartInfoTracker.handleProcessBroadcastStart(3, app, buildIntent(COMPONENT),
                 false /* isAlarm */);
+        // Add a brief delay between timestamps to make sure the clock, which is in milliseconds has
+        // actually incremented.
+        sleep(1);
         mAppStartInfoTracker.handleProcessBroadcastStart(2, app, buildIntent(COMPONENT),
                 false /* isAlarm */);
+        sleep(1);
         mAppStartInfoTracker.handleProcessBroadcastStart(1, app, buildIntent(COMPONENT),
                 false /* isAlarm */);
 
@@ -557,9 +561,10 @@
         // Now load from disk.
         mAppStartInfoTracker.loadExistingProcessStartInfo();
 
-        // Confirm clock has been set and that its current time is greater than the previous one.
+        // Confirm clock has been set and that its current time is greater than or equal to the
+        // previous one, thereby ensuring it was loaded from disk.
         assertNotNull(mAppStartInfoTracker.mMonotonicClock);
-        assertTrue(mAppStartInfoTracker.mMonotonicClock.monotonicTime() > originalMonotonicTime);
+        assertTrue(mAppStartInfoTracker.mMonotonicClock.monotonicTime() >= originalMonotonicTime);
     }
 
     private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index f6ad07d..2107406 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -229,8 +229,10 @@
         doCallRealMethod().when(mService).enqueueOomAdjTargetLocked(any(ProcessRecord.class));
         doCallRealMethod().when(mService).updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_ACTIVITY);
         setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object());
-        doReturn(new ActivityManagerService.ProcessChangeItem()).when(pr)
-                .enqueueProcessChangeItemLocked(anyInt(), anyInt());
+        doNothing().when(pr).enqueueProcessChangeItemLocked(anyInt(), anyInt(), anyInt(),
+                anyInt());
+        doNothing().when(pr).enqueueProcessChangeItemLocked(anyInt(), anyInt(), anyInt(),
+                anyBoolean());
         mService.mOomAdjuster = mService.mConstants.ENABLE_NEW_OOMADJ
                 ? new OomAdjusterModernImpl(mService, mService.mProcessList,
                         new ActiveUids(mService, false), mInjector)
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 b8f9767..130690d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -38,6 +38,7 @@
 import static android.app.Notification.FLAG_NO_DISMISS;
 import static android.app.Notification.FLAG_ONGOING_EVENT;
 import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
+import static android.app.Notification.FLAG_PROMOTED_ONGOING;
 import static android.app.Notification.FLAG_USER_INITIATED_JOB;
 import static android.app.Notification.GROUP_ALERT_CHILDREN;
 import static android.app.Notification.VISIBILITY_PRIVATE;
@@ -53,6 +54,7 @@
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MAX;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS;
@@ -468,6 +470,8 @@
 
     NotificationChannel mSilentChannel = new NotificationChannel("low", "low", IMPORTANCE_LOW);
 
+    NotificationChannel mMinChannel = new NotificationChannel("min", "min", IMPORTANCE_MIN);
+
     private static final int NOTIFICATION_LOCATION_UNKNOWN = 0;
 
     private static final String VALID_CONVO_SHORTCUT_ID = "shortcut";
@@ -558,8 +562,7 @@
 
     @Parameters(name = "{0}")
     public static List<FlagsParameterization> getParams() {
-        return FlagsParameterization.allCombinationsOf(
-                FLAG_ALL_NOTIFS_NEED_TTL);
+        return FlagsParameterization.allCombinationsOf();
     }
 
     public NotificationManagerServiceTest(FlagsParameterization flags) {
@@ -856,15 +859,17 @@
         mInternalService = mService.getInternalService();
 
         mBinderService.createNotificationChannels(mPkg, new ParceledListSlice(
-                Arrays.asList(mTestNotificationChannel, mSilentChannel)));
+                Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel)));
         mBinderService.createNotificationChannels(PKG_P, new ParceledListSlice(
-                Arrays.asList(mTestNotificationChannel, mSilentChannel)));
+                Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel)));
         mBinderService.createNotificationChannels(PKG_O, new ParceledListSlice(
-                Arrays.asList(mTestNotificationChannel, mSilentChannel)));
+                Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel)));
         assertNotNull(mBinderService.getNotificationChannel(
                 mPkg, mContext.getUserId(), mPkg, TEST_CHANNEL_ID));
         assertNotNull(mBinderService.getNotificationChannel(
                 mPkg, mContext.getUserId(), mPkg, mSilentChannel.getId()));
+        assertNotNull(mBinderService.getNotificationChannel(
+                mPkg, mContext.getUserId(), mPkg, mMinChannel.getId()));
         clearInvocations(mRankingHandler);
         when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
 
@@ -943,6 +948,16 @@
         }
     }
 
+    private ShortcutInfo createMockConvoShortcut() {
+        ShortcutInfo info = mock(ShortcutInfo.class);
+        when(info.getPackage()).thenReturn(mPkg);
+        when(info.getId()).thenReturn(VALID_CONVO_SHORTCUT_ID);
+        when(info.getUserId()).thenReturn(USER_SYSTEM);
+        when(info.isLongLived()).thenReturn(true);
+        when(info.isEnabled()).thenReturn(true);
+        return info;
+    }
+
     private void simulatePackageSuspendBroadcast(boolean suspend, String pkg,
             int uid) {
         // mimics receive broadcast that package is (un)suspended
@@ -16540,13 +16555,298 @@
         assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
     }
 
-    private ShortcutInfo createMockConvoShortcut() {
-        ShortcutInfo info = mock(ShortcutInfo.class);
-        when(info.getPackage()).thenReturn(mPkg);
-        when(info.getId()).thenReturn(VALID_CONVO_SHORTCUT_ID);
-        when(info.getUserId()).thenReturn(USER_SYSTEM);
-        when(info.isLongLived()).thenReturn(true);
-        when(info.isEnabled()).thenReturn(true);
-        return info;
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    public void testSetCanBePromoted_granted() throws Exception {
+        mContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+        // qualifying posted notification
+        Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+                .setColor(Color.WHITE)
+                .setColorized(true)
+                .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+                .build();
+
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        // qualifying enqueued notification
+        Notification n1 = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+                .setColor(Color.WHITE)
+                .setColorized(true)
+                .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+                .build();
+        StatusBarNotification sbn1 = new StatusBarNotification(mPkg, mPkg, 7, null, mUid, 0,
+                n1, UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mTestNotificationChannel);
+
+        // another package but otherwise would qualify
+        Notification n2 = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+                .setColor(Color.WHITE)
+                .setColorized(true)
+                .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+                .build();
+        StatusBarNotification sbn2 = new StatusBarNotification(PKG_O, PKG_O, 7, null, UID_O, 0,
+                n2, UserHandle.getUserHandleForUid(UID_O), null, 0);
+        NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mTestNotificationChannel);
+
+        // not-qualifying posted notification
+        Notification n3 = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .build();
+
+        StatusBarNotification sbn3 = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0,
+                n3, UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord r3 = new NotificationRecord(mContext, sbn3, mTestNotificationChannel);
+
+        mService.addNotification(r3);
+        mService.addNotification(r2);
+        mService.addNotification(r);
+        mService.addEnqueuedNotification(r1);
+
+        mBinderService.setCanBePromoted(mPkg, mUid, true);
+
+        waitForIdle();
+
+        ArgumentCaptor<NotificationRecord> captor =
+                ArgumentCaptor.forClass(NotificationRecord.class);
+        verify(mListeners, times(1)).prepareNotifyPostedLocked(
+                captor.capture(), any(), anyBoolean());
+
+        // the posted one
+        assertThat(mService.hasFlag(captor.getValue().getNotification().flags,
+                FLAG_PROMOTED_ONGOING)).isTrue();
+        // the enqueued one
+        assertThat(mService.hasFlag(r1.getNotification().flags, FLAG_PROMOTED_ONGOING)).isTrue();
+        // the other app
+        assertThat(mService.hasFlag(r2.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse();
+        // same app, not qualifying
+        assertThat(mService.hasFlag(r3.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse();
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    public void testSetCanBePromoted_granted_onlyNotifiesOnce() throws Exception {
+        mContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+        // qualifying posted notification
+        Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+                .setColor(Color.WHITE)
+                .setColorized(true)
+                .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+                .build();
+
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mService.addNotification(r);
+
+        mBinderService.setCanBePromoted(mPkg, mUid, true);
+        waitForIdle();
+        mBinderService.setCanBePromoted(mPkg, mUid, true);
+        waitForIdle();
+
+        ArgumentCaptor<NotificationRecord> captor =
+                ArgumentCaptor.forClass(NotificationRecord.class);
+        verify(mListeners, times(1)).prepareNotifyPostedLocked(
+                captor.capture(), any(), anyBoolean());
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    public void testSetCanBePromoted_revoked() throws Exception {
+        mContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+        // start from true state
+        mBinderService.setCanBePromoted(mPkg, mUid, true);
+
+        // qualifying posted notification
+        Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+                .setColor(Color.WHITE)
+                .setColorized(true)
+                .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post
+                .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+                .build();
+
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        // qualifying enqueued notification
+        Notification n1 = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+                .setColor(Color.WHITE)
+                .setColorized(true)
+                .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post
+                .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+                .build();
+        StatusBarNotification sbn1 = new StatusBarNotification(mPkg, mPkg, 7, null, mUid, 0,
+                n1, UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mTestNotificationChannel);
+
+        // doesn't qualify, same package
+        Notification n2 = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .build();
+        StatusBarNotification sbn2 = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0,
+                n2, UserHandle.getUserHandleForUid(UID_O), null, 0);
+        NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mTestNotificationChannel);
+
+        mService.addNotification(r2);
+        mService.addNotification(r);
+        mService.addEnqueuedNotification(r1);
+
+        mBinderService.setCanBePromoted(mPkg, mUid, false);
+
+        waitForIdle();
+
+        ArgumentCaptor<NotificationRecord> captor =
+                ArgumentCaptor.forClass(NotificationRecord.class);
+        verify(mListeners, times(1)).prepareNotifyPostedLocked(
+                captor.capture(), any(), anyBoolean());
+
+        // the posted one
+        assertThat(mService.hasFlag(captor.getValue().getNotification().flags,
+                FLAG_PROMOTED_ONGOING)).isFalse();
+        // the enqueued one
+        assertThat(mService.hasFlag(r1.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse();
+        // the not qualifying one
+        assertThat(mService.hasFlag(r2.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse();
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    public void testSetCanBePromoted_revoked_onlyNotifiesOnce() throws Exception {
+        mContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+        // start from true state
+        mBinderService.setCanBePromoted(mPkg, mUid, true);
+
+        // qualifying posted notification
+        Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+                .setColor(Color.WHITE)
+                .setColorized(true)
+                .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post
+                .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+                .build();
+
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mService.addNotification(r);
+
+        mBinderService.setCanBePromoted(mPkg, mUid, false);
+        waitForIdle();
+        mBinderService.setCanBePromoted(mPkg, mUid, false);
+        waitForIdle();
+
+        ArgumentCaptor<NotificationRecord> captor =
+                ArgumentCaptor.forClass(NotificationRecord.class);
+        verify(mListeners, times(1)).prepareNotifyPostedLocked(
+                captor.capture(), any(), anyBoolean());
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    public void testPostPromotableNotification() throws Exception {
+        mBinderService.setCanBePromoted(mPkg, mUid, true);
+        assertThat(mBinderService.canBePromoted(mPkg, mUid)).isTrue();
+        mContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+
+        Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+                .setColor(Color.WHITE)
+                .setColorized(true)
+                .build();
+        //assertThat(n.hasPromotableCharacteristics()).isTrue();
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
+
+        mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(),
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        waitForIdle();
+
+        ArgumentCaptor<NotificationRecord> captor =
+                ArgumentCaptor.forClass(NotificationRecord.class);
+        verify(mListeners, times(1)).prepareNotifyPostedLocked(
+                captor.capture(), any(), anyBoolean());
+
+        assertThat(mService.hasFlag(captor.getValue().getNotification().flags,
+                FLAG_PROMOTED_ONGOING)).isTrue();
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    public void testPostPromotableNotification_noPermission() throws Exception {
+        mContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+        Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+                .setColor(Color.WHITE)
+                .setColorized(true)
+                .build();
+
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
+
+        mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(),
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        waitForIdle();
+
+        ArgumentCaptor<NotificationRecord> captor =
+                ArgumentCaptor.forClass(NotificationRecord.class);
+        verify(mListeners, times(1)).prepareNotifyPostedLocked(
+                captor.capture(), any(), anyBoolean());
+
+        assertThat(mService.hasFlag(captor.getValue().getNotification().flags,
+                FLAG_PROMOTED_ONGOING)).isFalse();
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    public void testPostPromotableNotification_unimportantNotification() throws Exception {
+        mBinderService.setCanBePromoted(mPkg, mUid, true);
+        mContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+        Notification n = new Notification.Builder(mContext, mMinChannel.getId())
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+                .setColor(Color.WHITE)
+                .setColorized(true)
+                .build();
+
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
+
+        mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(),
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        waitForIdle();
+
+        ArgumentCaptor<NotificationRecord> captor =
+                ArgumentCaptor.forClass(NotificationRecord.class);
+        verify(mListeners, times(1)).prepareNotifyPostedLocked(
+                captor.capture(), any(), anyBoolean());
+
+        assertThat(mService.hasFlag(captor.getValue().getNotification().flags,
+                FLAG_PROMOTED_ONGOING)).isFalse();
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 1905ae4..7d63062 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -518,6 +518,17 @@
         doneLatch.await();
     }
 
+    private static NotificationChannel cloneChannel(NotificationChannel original) {
+        Parcel parcel = Parcel.obtain();
+        try {
+            original.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            return NotificationChannel.CREATOR.createFromParcel(parcel);
+        } finally {
+            parcel.recycle();
+        }
+    }
+
     @Test
     public void testWriteXml_onlyBackupsTargetUser() throws Exception {
         // Setup package notifications.
@@ -631,6 +642,9 @@
         }
 
         mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true);
+        if (android.app.Flags.uiRichOngoing()) {
+            mHelper.setCanBePromoted(PKG_N_MR1, UID_N_MR1, true);
+        }
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false,
                 UserHandle.USER_ALL, channel1.getId(), channel2.getId(),
@@ -641,6 +655,9 @@
         loadStreamXml(baos, false, UserHandle.USER_ALL);
 
         assertTrue(mXmlHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
+        if (android.app.Flags.uiRichOngoing()) {
+            assertThat(mXmlHelper.canBePromoted(PKG_N_MR1, UID_N_MR1)).isTrue();
+        }
         assertEquals(channel1,
                 mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false));
         compareChannels(channel2,
@@ -6293,14 +6310,21 @@
         }, 20, 50);
     }
 
-    private static NotificationChannel cloneChannel(NotificationChannel original) {
-        Parcel parcel = Parcel.obtain();
-        try {
-            original.writeToParcel(parcel, 0);
-            parcel.setDataPosition(0);
-            return NotificationChannel.CREATOR.createFromParcel(parcel);
-        } finally {
-            parcel.recycle();
-        }
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    public void testNoAppHasPermissionToPromoteByDefault() {
+        mHelper.setShowBadge(PKG_P, UID_P, true);
+        assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isFalse();
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+    public void testSetCanBePromoted() {
+        mHelper.setCanBePromoted(PKG_P, UID_P, true);
+        assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue();
+
+        mHelper.setCanBePromoted(PKG_P, UID_P, false);
+        assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isFalse();
+        verify(mHandler, never()).requestSort();
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 9b87947..d4cba8d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -18,7 +18,7 @@
 
 import static android.app.AutomaticZenRule.TYPE_BEDTIME;
 import static android.app.AutomaticZenRule.TYPE_IMMERSIVE;
-import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
+import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
 import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
 import static android.app.Flags.FLAG_MODES_API;
 import static android.app.Flags.FLAG_MODES_UI;
@@ -221,7 +221,7 @@
 @TestableLooper.RunWithLooper
 public class ZenModeHelperTest extends UiServiceTestCase {
 
-    private static final String EVENTS_DEFAULT_RULE_ID = ZenModeConfig.EVENTS_DEFAULT_RULE_ID;
+    private static final String EVENTS_DEFAULT_RULE_ID = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID;
     private static final String SCHEDULE_DEFAULT_RULE_ID =
             ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID;
     private static final String CUSTOM_PKG_NAME = "not.android";
@@ -1216,7 +1216,7 @@
 
         // list for tracking which ids we've seen in the pulled atom output
         List<String> ids = new ArrayList<>();
-        ids.addAll(ZenModeConfig.DEFAULT_RULE_IDS);
+        ids.addAll(ZenModeConfig.getDefaultRuleIds());
         ids.add("");  // empty string for root config
 
         for (StatsEvent ev : events) {
@@ -1793,14 +1793,13 @@
         // check default rules
         ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
         assertTrue(rules.size() != 0);
-        for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
+        for (String defaultId : ZenModeConfig.getDefaultRuleIds()) {
             assertTrue(rules.containsKey(defaultId));
         }
 
         assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
     }
 
-
     @Test
     public void testReadXmlAllDisabledRulesResetDefaultRules() throws Exception {
         setupZenConfig();
@@ -1830,7 +1829,7 @@
         // check default rules
         ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
         assertTrue(rules.size() != 0);
-        for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
+        for (String defaultId : ZenModeConfig.getDefaultRuleIds()) {
             assertTrue(rules.containsKey(defaultId));
         }
         assertFalse(rules.containsKey("customRule"));
@@ -1839,6 +1838,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_MODES_UI) // modes_ui has only 1 default rule
     public void testReadXmlOnlyOneDefaultRuleExists() throws Exception {
         setupZenConfig();
         Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
@@ -1882,11 +1882,11 @@
 
         // check default rules
         ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
-        assertTrue(rules.size() != 0);
-        for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
-            assertTrue(rules.containsKey(defaultId));
+        assertThat(rules).isNotEmpty();
+        for (String defaultId : ZenModeConfig.getDefaultRuleIds()) {
+            assertThat(rules).containsKey(defaultId);
         }
-        assertFalse(rules.containsKey("customRule"));
+        assertThat(rules).doesNotContainKey("customRule");
 
         assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
     }
@@ -1932,13 +1932,13 @@
         defaultEventRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         defaultEventRule.conditionId = ZenModeConfig.toScheduleConditionId(
                 defaultEventRuleInfo);
-        defaultEventRule.id = ZenModeConfig.EVENTS_DEFAULT_RULE_ID;
+        defaultEventRule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID;
         defaultScheduleRule.zenPolicy = new ZenPolicy.Builder()
                 .allowAlarms(false)
                 .allowMedia(false)
                 .allowRepeatCallers(false)
                 .build();
-        automaticRules.put(ZenModeConfig.EVENTS_DEFAULT_RULE_ID, defaultEventRule);
+        automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, defaultEventRule);
 
         mZenModeHelper.mConfig.automaticRules = automaticRules;
 
@@ -1951,18 +1951,19 @@
         mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
 
         // check default rules
+        int expectedNumAutoRules = 1 + ZenModeConfig.getDefaultRuleIds().size(); // custom + default
         ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
-        assertEquals(3, rules.size());
-        for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
-            assertTrue(rules.containsKey(defaultId));
+        assertThat(rules).hasSize(expectedNumAutoRules);
+        for (String defaultId : ZenModeConfig.getDefaultRuleIds()) {
+            assertThat(rules).containsKey(defaultId);
         }
-        assertTrue(rules.containsKey("customRule"));
+        assertThat(rules).containsKey("customRule");
 
         assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
 
         List<StatsEvent> events = new LinkedList<>();
         mZenModeHelper.pullRules(events);
-        assertEquals(4, events.size());
+        assertThat(events).hasSize(expectedNumAutoRules + 1); // auto + manual
     }
 
     @Test
@@ -2151,8 +2152,8 @@
         defaultEventRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         defaultEventRule.conditionId = ZenModeConfig.toScheduleConditionId(
                 defaultEventRuleInfo);
-        defaultEventRule.id = ZenModeConfig.EVENTS_DEFAULT_RULE_ID;
-        automaticRules.put(ZenModeConfig.EVENTS_DEFAULT_RULE_ID, defaultEventRule);
+        defaultEventRule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID;
+        automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, defaultEventRule);
 
         mZenModeHelper.mConfig.automaticRules = automaticRules;
 
@@ -2167,7 +2168,7 @@
         // check default rules
         ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
         assertThat(rules.size()).isGreaterThan(0);
-        for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
+        for (String defaultId : ZenModeConfig.getDefaultRuleIds()) {
             assertThat(rules).containsKey(defaultId);
             ZenRule rule = rules.get(defaultId);
             assertThat(rule.zenPolicy).isNotNull();
@@ -2371,7 +2372,7 @@
         // Find default rules; check they have non-null policies; check that they match the default
         // and not whatever has been set up in setupZenConfig.
         ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
-        for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
+        for (String defaultId : ZenModeConfig.getDefaultRuleIds()) {
             assertThat(rules).containsKey(defaultId);
             ZenRule rule = rules.get(defaultId);
             assertThat(rule.zenPolicy).isNotNull();
@@ -6884,7 +6885,7 @@
         mZenModeHelper.onUserSwitched(101);
 
         ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get(
-                ZenModeConfig.EVENTS_DEFAULT_RULE_ID);
+                ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
 
         assertThat(eventsRule).isNotNull();
         assertThat(eventsRule.zenPolicy).isNull();
@@ -6900,7 +6901,7 @@
         mZenModeHelper.onUserSwitched(201);
 
         ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get(
-                ZenModeConfig.EVENTS_DEFAULT_RULE_ID);
+                ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
 
         assertThat(eventsRule).isNotNull();
         assertThat(eventsRule.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy());
@@ -6915,11 +6916,11 @@
         mZenModeHelper.onUserSwitched(301);
 
         ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get(
-                ZenModeConfig.EVENTS_DEFAULT_RULE_ID);
+                ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
 
         assertThat(eventsRule).isNotNull();
         assertThat(eventsRule.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy());
-        assertThat(eventsRule.type).isEqualTo(TYPE_SCHEDULE_CALENDAR);
+        assertThat(eventsRule.type).isEqualTo(TYPE_SCHEDULE_TIME);
         assertThat(eventsRule.triggerDescription).isNotEmpty();
     }
 
@@ -6992,6 +6993,62 @@
         assertThat(zenRule.condition).isNotNull();
     }
 
+    @Test
+    @EnableFlags(FLAG_MODES_API)
+    public void addAutomaticZenRule_withoutPolicy_getsItsOwnInstanceOfDefaultPolicy() {
+        // Add a rule without policy -> uses default config
+        AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
+                .setPackage(mPkg)
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, azr, ORIGIN_APP, "adding",
+                CUSTOM_PKG_UID);
+
+        ZenRule zenRule = checkNotNull(mZenModeHelper.mConfig.automaticRules.get(ruleId));
+
+        assertThat(zenRule.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy());
+        assertThat(zenRule.zenPolicy).isNotSameInstanceAs(mZenModeHelper.getDefaultZenPolicy());
+    }
+
+    @Test
+    @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+    public void readXml_withDisabledEventsRule_deletesIt() throws Exception {
+        ZenRule rule = new ZenRule();
+        rule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID;
+        rule.name = "Events";
+        rule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        rule.conditionId = Uri.parse("events");
+
+        rule.enabled = false;
+        mZenModeHelper.mConfig.automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, rule);
+        ByteArrayOutputStream xmlBytes = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_UI);
+        TypedXmlPullParser parser = getParserForByteStream(xmlBytes);
+
+        mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+        assertThat(mZenModeHelper.mConfig.automaticRules).doesNotContainKey(
+                ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
+    }
+
+    @Test
+    @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+    public void readXml_withEnabledEventsRule_keepsIt() throws Exception {
+        ZenRule rule = new ZenRule();
+        rule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID;
+        rule.name = "Events";
+        rule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        rule.conditionId = Uri.parse("events");
+
+        rule.enabled = true;
+        mZenModeHelper.mConfig.automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, rule);
+        ByteArrayOutputStream xmlBytes = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_UI);
+        TypedXmlPullParser parser = getParserForByteStream(xmlBytes);
+
+        mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+        assertThat(mZenModeHelper.mConfig.automaticRules).containsKey(
+                ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
+    }
+
     private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode,
             @Nullable ZenPolicy zenPolicy) {
         ZenRule rule = new ZenRule();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 7ff2e50..4b03483 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -29,6 +29,8 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
 import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
@@ -75,6 +77,7 @@
 import android.app.ActivityOptions;
 import android.app.TaskInfo;
 import android.app.WindowConfiguration;
+import android.compat.testing.PlatformCompatChangeRule;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -97,9 +100,13 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
+import libcore.junit.util.compat.CoreCompatChangeRule;
+
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 import org.xmlpull.v1.XmlPullParser;
@@ -122,6 +129,9 @@
 @RunWith(WindowTestRunner.class)
 public class TaskTests extends WindowTestsBase {
 
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
     private static final String TASK_TAG = "task";
 
     private Rect mParentBounds;
@@ -404,6 +414,85 @@
     }
 
     @Test
+    @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP})
+    public void testIsResizeable_nonResizeable_forceResize_overridesEnabled_Resizeable() {
+        final Task task = new TaskBuilder(mSupervisor)
+                .setCreateActivity(true)
+                .setComponent(
+                        ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
+                .build();
+        task.setResizeMode(RESIZE_MODE_UNRESIZEABLE);
+        // Override should take effect and task should be resizeable.
+        assertTrue(task.getTaskInfo().isResizeable);
+    }
+
+    @Test
+    @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP})
+    public void testIsResizeable_nonResizeable_forceResize_overridesDisabled_nonResizeable() {
+        final Task task = new TaskBuilder(mSupervisor)
+                .setCreateActivity(true)
+                .setComponent(
+                        ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
+                .build();
+        task.setResizeMode(RESIZE_MODE_UNRESIZEABLE);
+
+        // Disallow resize overrides.
+        task.mAllowForceResizeOverride = false;
+
+        // Override should not take effect and task should be un-resizeable.
+        assertFalse(task.getTaskInfo().isResizeable);
+    }
+
+    @Test
+    @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
+    public void testIsResizeable_resizeable_forceNonResize_overridesEnabled_nonResizeable() {
+        final Task task = new TaskBuilder(mSupervisor)
+                .setCreateActivity(true)
+                .setComponent(
+                        ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
+                .build();
+        task.setResizeMode(RESIZE_MODE_RESIZEABLE);
+
+        // Override should take effect and task should be un-resizeable.
+        assertFalse(task.getTaskInfo().isResizeable);
+    }
+
+    @Test
+    @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
+    public void testIsResizeable_resizeable_forceNonResize_overridesDisabled_Resizeable() {
+        final Task task = new TaskBuilder(mSupervisor)
+                .setCreateActivity(true)
+                .setComponent(
+                        ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
+                .build();
+        task.setResizeMode(RESIZE_MODE_RESIZEABLE);
+
+        // Disallow resize overrides.
+        task.mAllowForceResizeOverride = false;
+
+        // Override should not take effect and task should be resizeable.
+        assertTrue(task.getTaskInfo().isResizeable);
+    }
+
+    @Test
+    @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
+    public void testIsResizeable_systemWideForceResize_compatForceNonResize__Resizeable() {
+        final Task task = new TaskBuilder(mSupervisor)
+                .setCreateActivity(true)
+                .setComponent(
+                        ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
+                .build();
+        task.setResizeMode(RESIZE_MODE_RESIZEABLE);
+
+        // Set system-wide force resizeable override.
+        task.mAtmService.mForceResizableActivities = true;
+
+        // System wide override should tak priority over app compat override so the task should
+        // remain resizeable.
+        assertTrue(task.getTaskInfo().isResizeable);
+    }
+
+    @Test
     public void testResolveNonResizableTaskWindowingMode() {
         // Test with no support non-resizable in multi window regardless the screen size.
         mAtm.mSupportsNonResizableMultiWindow = -1;
diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java
index a678147..0f4809c 100644
--- a/telephony/common/android/telephony/LocationAccessPolicy.java
+++ b/telephony/common/android/telephony/LocationAccessPolicy.java
@@ -33,6 +33,7 @@
 import android.widget.Toast;
 
 import com.android.internal.telephony.TelephonyPermissions;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.util.TelephonyUtils;
 
 /**
@@ -283,6 +284,8 @@
         int minSdkVersion = Manifest.permission.ACCESS_FINE_LOCATION.equals(permissionToCheck)
                 ? query.minSdkVersionForFine : query.minSdkVersionForCoarse;
 
+        UserHandle callingUserHandle = UserHandle.getUserHandleForUid(query.callingUid);
+
         // If the app fails for some reason, see if it should be allowed to proceed.
         if (minSdkVersion > MAX_SDK_FOR_ANY_ENFORCEMENT) {
             String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog
@@ -291,7 +294,8 @@
                     + query.method;
             logError(context, query, errorMsg);
             return null;
-        } else if (!isAppAtLeastSdkVersion(context, query.callingPackage, minSdkVersion)) {
+        } else if (!isAppAtLeastSdkVersion(context, callingUserHandle, query.callingPackage,
+                minSdkVersion)) {
             String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog
                     + " because it doesn't target API " + minSdkVersion + " yet."
                     + " Please fix this app. Called from " + query.method;
@@ -420,11 +424,19 @@
         }
     }
 
-    private static boolean isAppAtLeastSdkVersion(Context context, String pkgName, int sdkVersion) {
+    private static boolean isAppAtLeastSdkVersion(Context context,
+            @NonNull UserHandle callingUserHandle, String pkgName, int sdkVersion) {
         try {
-            if (context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion
-                    >= sdkVersion) {
-                return true;
+            if (Flags.hsumPackageManager()) {
+                if (context.getPackageManager().getApplicationInfoAsUser(
+                        pkgName, 0, callingUserHandle).targetSdkVersion >= sdkVersion) {
+                    return true;
+                }
+            } else {
+                if (context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion
+                        >= sdkVersion) {
+                    return true;
+                }
             }
         } catch (PackageManager.NameNotFoundException e) {
             // In case of exception, assume known app (more strict checking)
diff --git a/tests/FlickerTests/ActivityEmbedding/OWNERS b/tests/FlickerTests/ActivityEmbedding/OWNERS
new file mode 100644
index 0000000..981b316
--- /dev/null
+++ b/tests/FlickerTests/ActivityEmbedding/OWNERS
@@ -0,0 +1 @@
+# Bug component: 1168918
diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
index b3a998e..b5258df 100644
--- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
@@ -57,6 +57,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.function.Consumer;
+
 /**
  * Build/Install/Run:
  * atest TouchpadDebugViewTest
@@ -99,10 +101,12 @@
 
         when(mInputManager.getInputDevice(TOUCHPAD_DEVICE_ID)).thenReturn(inputDevice);
 
+        Consumer<Integer> touchpadSwitchHandler = id -> {};
+
         mTouchpadDebugView = new TouchpadDebugView(mTestableContext, TOUCHPAD_DEVICE_ID,
                 new TouchpadHardwareProperties.Builder(0f, 0f, 500f,
                         500f, 45f, 47f, -4f, 5f, (short) 10, true,
-                        true).build());
+                        true).build(), touchpadSwitchHandler);
 
         mTouchpadDebugView.measure(
                 View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
@@ -321,26 +325,30 @@
                 new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0,
                         new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);
 
-        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE);
+        assertEquals(((ColorDrawable) child.getBackground()).getColor(),
+                Color.parseColor("#769763"));
 
         mTouchpadDebugView.updateHardwareState(
                 new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
                         new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);
 
-        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.RED);
+        assertEquals(((ColorDrawable) child.getBackground()).getColor(),
+                Color.parseColor("#5455A9"));
 
         mTouchpadDebugView.updateHardwareState(
                 new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0,
                         new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);
 
-        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE);
+        assertEquals(((ColorDrawable) child.getBackground()).getColor(),
+                Color.parseColor("#769763"));
 
         // Color should not change because hardware state of a different touchpad
         mTouchpadDebugView.updateHardwareState(
                 new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
                         new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID + 1);
 
-        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE);
+        assertEquals(((ColorDrawable) child.getBackground()).getColor(),
+                Color.parseColor("#769763"));
     }
 
     @Test
diff --git a/tests/broadcasts/OWNERS b/tests/broadcasts/OWNERS
new file mode 100644
index 0000000..d2e1f81
--- /dev/null
+++ b/tests/broadcasts/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 316181
+include platform/frameworks/base:/BROADCASTS_OWNERS
diff --git a/tests/broadcasts/unit/Android.bp b/tests/broadcasts/unit/Android.bp
new file mode 100644
index 0000000..0692477
--- /dev/null
+++ b/tests/broadcasts/unit/Android.bp
@@ -0,0 +1,44 @@
+//
+// 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "BroadcastUnitTests",
+    srcs: ["src/**/*.java"],
+    defaults: [
+        "modules-utils-extended-mockito-rule-defaults",
+    ],
+    static_libs: [
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "mockito-target-extended-minus-junit4",
+        "truth",
+        "flag-junit",
+        "android.app.flags-aconfig-java",
+    ],
+    certificate: "platform",
+    platform_apis: true,
+    test_suites: ["device-tests"],
+}
diff --git a/tests/broadcasts/unit/AndroidManifest.xml b/tests/broadcasts/unit/AndroidManifest.xml
new file mode 100644
index 0000000..e9c5248
--- /dev/null
+++ b/tests/broadcasts/unit/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.broadcasts.unit" >
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.broadcasts.unit"
+            android:label="Broadcasts Unit Tests"/>
+</manifest>
\ No newline at end of file
diff --git a/tests/broadcasts/unit/AndroidTest.xml b/tests/broadcasts/unit/AndroidTest.xml
new file mode 100644
index 0000000..b91e4783
--- /dev/null
+++ b/tests/broadcasts/unit/AndroidTest.xml
@@ -0,0 +1,29 @@
+<!-- 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.
+-->
+<configuration description="Runs Broadcasts tests">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-tag" value="BroadcastUnitTests" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="BroadcastUnitTests.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.broadcasts.unit" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/broadcasts/unit/TEST_MAPPING b/tests/broadcasts/unit/TEST_MAPPING
new file mode 100644
index 0000000..0e824c5
--- /dev/null
+++ b/tests/broadcasts/unit/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+    "postsubmit": [
+        {
+            "name": "BroadcastUnitTests",
+            "options": [
+                {
+                    "exclude-annotation": "androidx.test.filters.FlakyTest"
+                },
+                {
+                    "exclude-annotation": "org.junit.Ignore"
+                }
+            ]
+        }
+    ]
+}
diff --git a/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java b/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java
new file mode 100644
index 0000000..b7c412d
--- /dev/null
+++ b/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java
@@ -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 android.app;
+
+import static android.content.Intent.ACTION_BATTERY_CHANGED;
+import static android.content.Intent.ACTION_DEVICE_STORAGE_LOW;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.ArrayMap;
+
+import androidx.annotation.GuardedBy;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+@EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BroadcastStickyCacheTest {
+    @ClassRule
+    public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
+
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+            .mockStatic(SystemProperties.class)
+            .build();
+
+    private static final String PROP_KEY_BATTERY_CHANGED = BroadcastStickyCache.getKey(
+            ACTION_BATTERY_CHANGED);
+
+    private final TestSystemProps mTestSystemProps = new TestSystemProps();
+
+    @Before
+    public void setUp() {
+        doAnswer(invocation -> {
+            final String name = invocation.getArgument(0);
+            final long value = Long.parseLong(invocation.getArgument(1));
+            mTestSystemProps.add(name, value);
+            return null;
+        }).when(() -> SystemProperties.set(anyString(), anyString()));
+        doAnswer(invocation -> {
+            final String name = invocation.getArgument(0);
+            final TestSystemProps.Handle testHandle = mTestSystemProps.query(name);
+            if (testHandle == null) {
+                return null;
+            }
+            final SystemProperties.Handle handle = Mockito.mock(SystemProperties.Handle.class);
+            doAnswer(handleInvocation -> testHandle.getLong(-1)).when(handle).getLong(anyLong());
+            return handle;
+        }).when(() -> SystemProperties.find(anyString()));
+    }
+
+    @After
+    public void tearDown() {
+        mTestSystemProps.clear();
+        BroadcastStickyCache.clearForTest();
+    }
+
+    @Test
+    public void testUseCache_nullFilter() {
+        assertThat(BroadcastStickyCache.useCache(null)).isEqualTo(false);
+    }
+
+    @Test
+    public void testUseCache_noActions() {
+        final IntentFilter filter = new IntentFilter();
+        assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
+    }
+
+    @Test
+    public void testUseCache_multipleActions() {
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_DEVICE_STORAGE_LOW);
+        filter.addAction(ACTION_BATTERY_CHANGED);
+        assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
+    }
+
+    @Test
+    public void testUseCache_valueNotSet() {
+        final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
+        assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
+    }
+
+    @Test
+    public void testUseCache() {
+        final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
+        final Intent intent = new Intent(ACTION_BATTERY_CHANGED)
+                .putExtra(BatteryManager.EXTRA_LEVEL, 90);
+        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+        BroadcastStickyCache.add(filter, intent);
+        assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(true);
+    }
+
+    @Test
+    public void testUseCache_versionMismatch() {
+        final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
+        final Intent intent = new Intent(ACTION_BATTERY_CHANGED)
+                .putExtra(BatteryManager.EXTRA_LEVEL, 90);
+        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+        BroadcastStickyCache.add(filter, intent);
+        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+
+        assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
+    }
+
+    @Test
+    public void testAdd() {
+        final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
+        Intent intent = new Intent(ACTION_BATTERY_CHANGED)
+                .putExtra(BatteryManager.EXTRA_LEVEL, 90);
+        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+        BroadcastStickyCache.add(filter, intent);
+        assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(true);
+        Intent actualIntent = BroadcastStickyCache.getIntentUnchecked(filter);
+        assertThat(actualIntent).isNotNull();
+        assertEquals(actualIntent, intent);
+
+        intent = new Intent(ACTION_BATTERY_CHANGED)
+                .putExtra(BatteryManager.EXTRA_LEVEL, 99);
+        BroadcastStickyCache.add(filter, intent);
+        actualIntent = BroadcastStickyCache.getIntentUnchecked(filter);
+        assertThat(actualIntent).isNotNull();
+        assertEquals(actualIntent, intent);
+    }
+
+    @Test
+    public void testIncrementVersion_propExists() {
+        SystemProperties.set(PROP_KEY_BATTERY_CHANGED, String.valueOf(100));
+
+        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(101);
+        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(102);
+    }
+
+    @Test
+    public void testIncrementVersion_propNotExists() {
+        // Verify that the property doesn't exist
+        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
+
+        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(1);
+        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(2);
+    }
+
+    @Test
+    public void testIncrementVersionIfExists_propExists() {
+        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+
+        BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
+        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(2);
+        BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
+        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(3);
+    }
+
+    @Test
+    public void testIncrementVersionIfExists_propNotExists() {
+        // Verify that the property doesn't exist
+        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
+
+        BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
+        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
+        // Verify that property is not added as part of the querying.
+        BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
+        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
+    }
+
+    private void assertEquals(Intent actualIntent, Intent expectedIntent) {
+        assertThat(actualIntent.getAction()).isEqualTo(expectedIntent.getAction());
+        assertEquals(actualIntent.getExtras(), expectedIntent.getExtras());
+    }
+
+    private void assertEquals(Bundle actualExtras, Bundle expectedExtras) {
+        assertWithMessage("Extras expected=%s, actual=%s", expectedExtras, actualExtras)
+                .that(actualExtras.kindofEquals(expectedExtras)).isTrue();
+    }
+
+    private static final class TestSystemProps {
+        @GuardedBy("mSysProps")
+        private final ArrayMap<String, Long> mSysProps = new ArrayMap<>();
+
+        public void add(String name, long value) {
+            synchronized (mSysProps) {
+                mSysProps.put(name, value);
+            }
+        }
+
+        public long get(String name, long defaultValue) {
+            synchronized (mSysProps) {
+                final int idx = mSysProps.indexOfKey(name);
+                return idx >= 0 ? mSysProps.valueAt(idx) : defaultValue;
+            }
+        }
+
+        public Handle query(String name) {
+            synchronized (mSysProps) {
+                return mSysProps.containsKey(name) ? new Handle(name) : null;
+            }
+        }
+
+        public void clear() {
+            synchronized (mSysProps) {
+                mSysProps.clear();
+            }
+        }
+
+        public class Handle {
+            private final String mName;
+
+            Handle(String name) {
+                mName = name;
+            }
+
+            public long getLong(long defaultValue) {
+                return get(mName, defaultValue);
+            }
+        }
+    }
+}
diff --git a/tools/systemfeatures/Android.bp b/tools/systemfeatures/Android.bp
index a9e6328..590f719 100644
--- a/tools/systemfeatures/Android.bp
+++ b/tools/systemfeatures/Android.bp
@@ -30,8 +30,8 @@
     name: "systemfeatures-gen-tests-srcs",
     cmd: "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwNoFeatures --readonly=false > $(location RwNoFeatures.java) && " +
         "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoNoFeatures --readonly=true --feature-apis=WATCH > $(location RoNoFeatures.java) && " +
-        "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwFeatures --readonly=false --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:-1 --feature=AUTO: > $(location RwFeatures.java) && " +
-        "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoFeatures --readonly=true --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:-1 --feature=AUTO: --feature-apis=WATCH,PC > $(location RoFeatures.java)",
+        "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwFeatures --readonly=false --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:UNAVAILABLE --feature=AUTO: > $(location RwFeatures.java) && " +
+        "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoFeatures --readonly=true --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:UNAVAILABLE --feature=AUTO: --feature-apis=WATCH,PC > $(location RoFeatures.java)",
     out: [
         "RwNoFeatures.java",
         "RoNoFeatures.java",
diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
index 5df453d..cba521e 100644
--- a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
+++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
@@ -20,7 +20,10 @@
 import com.squareup.javapoet.ClassName
 import com.squareup.javapoet.JavaFile
 import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterizedTypeName
 import com.squareup.javapoet.TypeSpec
+import java.util.HashMap
+import java.util.Map
 import javax.lang.model.element.Modifier
 
 /*
@@ -31,7 +34,7 @@
  *
  * <pre>
  *   <cmd> com.foo.RoSystemFeatures --readonly=true \
- *           --feature=WATCH:0 --feature=AUTOMOTIVE: --feature=VULKAN:9348
+ *           --feature=WATCH:0 --feature=AUTOMOTIVE: --feature=VULKAN:9348 --feature=PC:UNAVAILABLE
  *           --feature-apis=WATCH,PC,LEANBACK
  * </pre>
  *
@@ -43,12 +46,13 @@
  *     @AssumeTrueForR8
  *     public static boolean hasFeatureWatch(Context context);
  *     @AssumeFalseForR8
- *     public static boolean hasFeatureAutomotive(Context context);
+ *     public static boolean hasFeaturePc(Context context);
  *     @AssumeTrueForR8
  *     public static boolean hasFeatureVulkan(Context context);
- *     public static boolean hasFeaturePc(Context context);
+ *     public static boolean hasFeatureAutomotive(Context context);
  *     public static boolean hasFeatureLeanback(Context context);
  *     public static Boolean maybeHasFeature(String feature, int version);
+ *     public static ArrayMap<String, FeatureInfo> getCompileTimeAvailableFeatures();
  * }
  * </pre>
  */
@@ -58,6 +62,7 @@
     private const val READONLY_ARG = "--readonly="
     private val PACKAGEMANAGER_CLASS = ClassName.get("android.content.pm", "PackageManager")
     private val CONTEXT_CLASS = ClassName.get("android.content", "Context")
+    private val FEATUREINFO_CLASS = ClassName.get("android.content.pm", "FeatureInfo")
     private val ASSUME_TRUE_CLASS =
         ClassName.get("com.android.aconfig.annotations", "AssumeTrueForR8")
     private val ASSUME_FALSE_CLASS =
@@ -67,7 +72,10 @@
         println("Usage: SystemFeaturesGenerator <outputClassName> [options]")
         println(" Options:")
         println("  --readonly=true|false    Whether to encode features as build-time constants")
-        println("  --feature=\$NAME:\$VER   A feature+version pair (blank version == disabled)")
+        println("  --feature=\$NAME:\$VER   A feature+version pair, where \$VER can be:")
+        println("                             * blank/empty == undefined (variable API)")
+        println("                             * valid int   == enabled   (constant API)")
+        println("                             * UNAVAILABLE == disabled  (constant API)")
         println("                           This will always generate associated query APIs,")
         println("                           adding to or replacing those from `--feature-apis=`.")
         println("  --feature-apis=\$NAME_1,\$NAME_2")
@@ -89,7 +97,7 @@
 
         var readonly = false
         var outputClassName: ClassName? = null
-        val featureArgs = mutableListOf<FeatureArg>()
+        val featureArgs = mutableListOf<FeatureInfo>()
         // We could just as easily hardcode this list, as the static API surface should change
         // somewhat infrequently, but this decouples the codegen from the framework completely.
         val featureApiArgs = mutableSetOf<String>()
@@ -122,7 +130,7 @@
         featureArgs.associateByTo(
             features,
             { it.name },
-            { FeatureInfo(it.name, it.version, readonly) },
+            { FeatureInfo(it.name, it.version, it.readonly && readonly) },
         )
 
         outputClassName
@@ -139,6 +147,7 @@
 
         addFeatureMethodsToClass(classBuilder, features.values)
         addMaybeFeatureMethodToClass(classBuilder, features.values)
+        addGetFeaturesMethodToClass(classBuilder, features.values)
 
         // TODO(b/203143243): Add validation of build vs runtime values to ensure consistency.
         JavaFile.builder(outputClassName.packageName(), classBuilder.build())
@@ -154,13 +163,17 @@
      * Parses a feature argument of the form "--feature=$NAME:$VER", where "$VER" is optional.
      *   * "--feature=WATCH:0" -> Feature enabled w/ version 0 (default version when enabled)
      *   * "--feature=WATCH:7" -> Feature enabled w/ version 7
-     *   * "--feature=WATCH:"  -> Feature disabled
+     *   * "--feature=WATCH:"  -> Feature status undefined, runtime API generated
+     *   * "--feature=WATCH:UNAVAILABLE"  -> Feature disabled
      */
-    private fun parseFeatureArg(arg: String): FeatureArg {
+    private fun parseFeatureArg(arg: String): FeatureInfo {
         val featureArgs = arg.substring(FEATURE_ARG.length).split(":")
         val name = parseFeatureName(featureArgs[0])
-        val version = featureArgs.getOrNull(1)?.toIntOrNull()
-        return FeatureArg(name, version)
+        return when (featureArgs.getOrNull(1)) {
+            null, "" -> FeatureInfo(name, null, readonly = false)
+            "UNAVAILABLE" -> FeatureInfo(name, null, readonly = true)
+            else -> FeatureInfo(name, featureArgs[1].toIntOrNull(), readonly = true)
+        }
     }
 
     private fun parseFeatureName(name: String): String =
@@ -218,7 +231,7 @@
     /*
      * Adds a generic query method to the class with the form: {@code public static boolean
      * maybeHasFeature(String featureName, int version)}, returning null if the feature version is
-     * undefined or not readonly.
+     * undefined or not (compile-time) readonly.
      *
      * This method is useful for internal usage within the framework, e.g., from the implementation
      * of {@link android.content.pm.PackageManager#hasSystemFeature(Context)}, when we may only
@@ -267,7 +280,41 @@
         builder.addMethod(methodBuilder.build())
     }
 
-    private data class FeatureArg(val name: String, val version: Int?)
+    /*
+     * Adds a method to get all compile-time enabled features.
+     *
+     * This method is useful for internal usage within the framework to augment
+     * any system features that are parsed from the various partitions.
+     */
+    private fun addGetFeaturesMethodToClass(
+        builder: TypeSpec.Builder,
+        features: Collection<FeatureInfo>,
+    ) {
+        val methodBuilder =
+                MethodSpec.methodBuilder("getCompileTimeAvailableFeatures")
+                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+                .addAnnotation(ClassName.get("android.annotation", "NonNull"))
+                .addJavadoc("Gets features marked as available at compile-time, keyed by name." +
+                        "\n\n@hide")
+                .returns(ParameterizedTypeName.get(
+                        ClassName.get(Map::class.java),
+                        ClassName.get(String::class.java),
+                        FEATUREINFO_CLASS))
+
+        val availableFeatures = features.filter { it.readonly && it.version != null }
+        methodBuilder.addStatement("Map<String, FeatureInfo> features = new \$T<>(\$L)",
+                HashMap::class.java, availableFeatures.size)
+        if (!availableFeatures.isEmpty()) {
+            methodBuilder.addStatement("FeatureInfo fi = new FeatureInfo()")
+        }
+        for (feature in availableFeatures) {
+            methodBuilder.addStatement("fi.name = \$T.\$N", PACKAGEMANAGER_CLASS, feature.name)
+            methodBuilder.addStatement("fi.version = \$L", feature.version)
+            methodBuilder.addStatement("features.put(fi.name, new FeatureInfo(fi))")
+        }
+        methodBuilder.addStatement("return features")
+        builder.addMethod(methodBuilder.build())
+    }
 
     private data class FeatureInfo(val name: String, val version: Int?, val readonly: Boolean)
 }
diff --git a/tools/systemfeatures/tests/golden/RoFeatures.java.gen b/tools/systemfeatures/tests/golden/RoFeatures.java.gen
index 724639b..edbfc42 100644
--- a/tools/systemfeatures/tests/golden/RoFeatures.java.gen
+++ b/tools/systemfeatures/tests/golden/RoFeatures.java.gen
@@ -3,16 +3,20 @@
 //            --readonly=true \
 //            --feature=WATCH:1 \
 //            --feature=WIFI:0 \
-//            --feature=VULKAN:-1 \
+//            --feature=VULKAN:UNAVAILABLE \
 //            --feature=AUTO: \
 //            --feature-apis=WATCH,PC
 package com.android.systemfeatures;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.FeatureInfo;
 import android.content.pm.PackageManager;
 import com.android.aconfig.annotations.AssumeFalseForR8;
 import com.android.aconfig.annotations.AssumeTrueForR8;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * @hide
@@ -62,9 +66,8 @@
      *
      * @hide
      */
-    @AssumeFalseForR8
     public static boolean hasFeatureAuto(Context context) {
-        return false;
+        return hasFeatureFallback(context, PackageManager.FEATURE_AUTO);
     }
 
     private static boolean hasFeatureFallback(Context context, String featureName) {
@@ -79,10 +82,27 @@
         switch (featureName) {
             case PackageManager.FEATURE_WATCH: return 1 >= version;
             case PackageManager.FEATURE_WIFI: return 0 >= version;
-            case PackageManager.FEATURE_VULKAN: return -1 >= version;
-            case PackageManager.FEATURE_AUTO: return false;
+            case PackageManager.FEATURE_VULKAN: return false;
             default: break;
         }
         return null;
     }
+
+    /**
+     * Gets features marked as available at compile-time, keyed by name.
+     *
+     * @hide
+     */
+    @NonNull
+    public static Map<String, FeatureInfo> getCompileTimeAvailableFeatures() {
+        Map<String, FeatureInfo> features = new HashMap<>(2);
+        FeatureInfo fi = new FeatureInfo();
+        fi.name = PackageManager.FEATURE_WATCH;
+        fi.version = 1;
+        features.put(fi.name, new FeatureInfo(fi));
+        fi.name = PackageManager.FEATURE_WIFI;
+        fi.version = 0;
+        features.put(fi.name, new FeatureInfo(fi));
+        return features;
+    }
 }
diff --git a/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen b/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen
index 59c5b4e..bf7a006 100644
--- a/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen
+++ b/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen
@@ -4,9 +4,13 @@
 //            --feature-apis=WATCH
 package com.android.systemfeatures;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.FeatureInfo;
 import android.content.pm.PackageManager;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * @hide
@@ -32,4 +36,15 @@
     public static Boolean maybeHasFeature(String featureName, int version) {
         return null;
     }
+
+    /**
+     * Gets features marked as available at compile-time, keyed by name.
+     *
+     * @hide
+     */
+    @NonNull
+    public static Map<String, FeatureInfo> getCompileTimeAvailableFeatures() {
+        Map<String, FeatureInfo> features = new HashMap<>(0);
+        return features;
+    }
 }
diff --git a/tools/systemfeatures/tests/golden/RwFeatures.java.gen b/tools/systemfeatures/tests/golden/RwFeatures.java.gen
index 6f89759..b20b228 100644
--- a/tools/systemfeatures/tests/golden/RwFeatures.java.gen
+++ b/tools/systemfeatures/tests/golden/RwFeatures.java.gen
@@ -3,13 +3,17 @@
 //            --readonly=false \
 //            --feature=WATCH:1 \
 //            --feature=WIFI:0 \
-//            --feature=VULKAN:-1 \
+//            --feature=VULKAN:UNAVAILABLE \
 //            --feature=AUTO:
 package com.android.systemfeatures;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.FeatureInfo;
 import android.content.pm.PackageManager;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * @hide
@@ -62,4 +66,15 @@
     public static Boolean maybeHasFeature(String featureName, int version) {
         return null;
     }
+
+    /**
+     * Gets features marked as available at compile-time, keyed by name.
+     *
+     * @hide
+     */
+    @NonNull
+    public static Map<String, FeatureInfo> getCompileTimeAvailableFeatures() {
+        Map<String, FeatureInfo> features = new HashMap<>(0);
+        return features;
+    }
 }
diff --git a/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen b/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen
index 2111d56..d91f5b6 100644
--- a/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen
+++ b/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen
@@ -3,8 +3,12 @@
 //            --readonly=false
 package com.android.systemfeatures;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.FeatureInfo;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * @hide
@@ -21,4 +25,15 @@
     public static Boolean maybeHasFeature(String featureName, int version) {
         return null;
     }
+
+    /**
+     * Gets features marked as available at compile-time, keyed by name.
+     *
+     * @hide
+     */
+    @NonNull
+    public static Map<String, FeatureInfo> getCompileTimeAvailableFeatures() {
+        Map<String, FeatureInfo> features = new HashMap<>(0);
+        return features;
+    }
 }
diff --git a/tools/systemfeatures/tests/src/FeatureInfo.java b/tools/systemfeatures/tests/src/FeatureInfo.java
new file mode 100644
index 0000000..9d57edc
--- /dev/null
+++ b/tools/systemfeatures/tests/src/FeatureInfo.java
@@ -0,0 +1,30 @@
+/*
+ * 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.content.pm;
+
+/** Stub for testing */
+public final class FeatureInfo {
+    public String name;
+    public int version;
+
+    public FeatureInfo() {}
+
+    public FeatureInfo(FeatureInfo orig) {
+        name = orig.name;
+        version = orig.version;
+    }
+}
diff --git a/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java
index 6dfd244..39f8fc4 100644
--- a/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java
+++ b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.pm.FeatureInfo;
 import android.content.pm.PackageManager;
 
 import org.junit.Before;
@@ -36,6 +37,8 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.Map;
+
 @RunWith(JUnit4.class)
 public class SystemFeaturesGeneratorTest {
 
@@ -57,6 +60,7 @@
         assertThat(RwNoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull();
         assertThat(RwNoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull();
         assertThat(RwNoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull();
+        assertThat(RwNoFeatures.getCompileTimeAvailableFeatures()).isEmpty();
     }
 
     @Test
@@ -68,6 +72,7 @@
         assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull();
         assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull();
         assertThat(RoNoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull();
+        assertThat(RoNoFeatures.getCompileTimeAvailableFeatures()).isEmpty();
 
         // Also ensure we fall back to the PackageManager for feature APIs without an accompanying
         // versioned feature definition.
@@ -101,6 +106,7 @@
         assertThat(RwFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull();
         assertThat(RwFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull();
         assertThat(RwFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull();
+        assertThat(RwFeatures.getCompileTimeAvailableFeatures()).isEmpty();
     }
 
     @Test
@@ -110,10 +116,13 @@
         assertThat(RoFeatures.hasFeatureWatch(mContext)).isTrue();
         assertThat(RoFeatures.hasFeatureWifi(mContext)).isTrue();
         assertThat(RoFeatures.hasFeatureVulkan(mContext)).isFalse();
-        assertThat(RoFeatures.hasFeatureAuto(mContext)).isFalse();
         verify(mPackageManager, never()).hasSystemFeature(anyString(), anyInt());
 
-        // For defined feature types, conditional queries should reflect the build-time versions.
+        // For defined feature types, conditional queries should reflect either:
+        //  * Enabled if the feature version is specified
+        //  * Disabled if UNAVAILABLE is specified
+        //  * Unknown if no version value is provided
+
         // VERSION=1
         assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, -1)).isTrue();
         assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, 0)).isTrue();
@@ -124,15 +133,19 @@
         assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, 0)).isTrue();
         assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, 100)).isFalse();
 
-        // VERSION=-1
-        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, -1)).isTrue();
+        // VERSION=UNAVAILABLE
+        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, -1)).isFalse();
         assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isFalse();
         assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 100)).isFalse();
 
-        // DISABLED
-        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, -1)).isFalse();
-        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isFalse();
-        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 100)).isFalse();
+        // VERSION=
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTO, 0)).thenReturn(false);
+        assertThat(RoFeatures.hasFeatureAuto(mContext)).isFalse();
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTO, 0)).thenReturn(true);
+        assertThat(RoFeatures.hasFeatureAuto(mContext)).isTrue();
+        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, -1)).isNull();
+        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull();
+        assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 100)).isNull();
 
         // For feature APIs without an associated feature definition, conditional queries should
         // report null, and explicit queries should report runtime-defined versions.
@@ -148,5 +161,12 @@
         assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", -1)).isNull();
         assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull();
         assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", 100)).isNull();
+        assertThat(RoFeatures.maybeHasFeature("", 0)).isNull();
+
+        Map<String, FeatureInfo> compiledFeatures = RoFeatures.getCompileTimeAvailableFeatures();
+        assertThat(compiledFeatures.keySet())
+                .containsExactly(PackageManager.FEATURE_WATCH, PackageManager.FEATURE_WIFI);
+        assertThat(compiledFeatures.get(PackageManager.FEATURE_WATCH).version).isEqualTo(1);
+        assertThat(compiledFeatures.get(PackageManager.FEATURE_WIFI).version).isEqualTo(0);
     }
 }