Merge "Update writer API flag" into main
diff --git a/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java b/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java
index c775280..ed669be 100644
--- a/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java
@@ -63,12 +63,14 @@
 
     @Test
     @Parameters(method = "getData")
-    public void timeZipFileOpenClose(int numEntries) throws Exception {
+    public void timeZipFileOpen(int numEntries) throws Exception {
         setUp(numEntries);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             ZipFile zf = new ZipFile(mFile);
+            state.pauseTiming();
             zf.close();
+            state.resumeTiming();
         }
     }
 
diff --git a/api/api.go b/api/api.go
index e4d783e..cbdb7e8 100644
--- a/api/api.go
+++ b/api/api.go
@@ -105,7 +105,7 @@
 
 func (a *CombinedApis) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	ctx.WalkDeps(func(child, parent android.Module) bool {
-		if _, ok := child.(java.AndroidLibraryDependency); ok && child.Name() != "framework-res" {
+		if _, ok := android.OtherModuleProvider(ctx, child, java.AndroidLibraryInfoProvider); ok && child.Name() != "framework-res" {
 			// Stubs of BCP and SSCP libraries should not have any dependencies on apps
 			// This check ensures that we do not run into circular dependencies when UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT=true
 			ctx.ModuleErrorf(
diff --git a/core/api/current.txt b/core/api/current.txt
index 1b494c5..7c56a58 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9190,37 +9190,37 @@
 package android.app.jank {
 
   @FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public final class AppJankStats {
-    ctor public AppJankStats(int, @NonNull String, @Nullable String, @Nullable String, long, long, @NonNull android.app.jank.FrameOverrunHistogram);
-    method @NonNull public android.app.jank.FrameOverrunHistogram getFrameOverrunHistogram();
+    ctor public AppJankStats(int, @NonNull String, @Nullable String, @Nullable String, long, long, @NonNull android.app.jank.RelativeFrameTimeHistogram);
     method public long getJankyFrameCount();
+    method @NonNull public android.app.jank.RelativeFrameTimeHistogram getRelativeFrameTimeHistogram();
     method public long getTotalFrameCount();
     method public int getUid();
     method @NonNull public String getWidgetCategory();
     method @NonNull public String getWidgetId();
     method @NonNull public String getWidgetState();
-    field public static final String ANIMATING = "animating";
-    field public static final String ANIMATION = "animation";
-    field public static final String DRAGGING = "dragging";
-    field public static final String FLINGING = "flinging";
-    field public static final String KEYBOARD = "keyboard";
-    field public static final String MEDIA = "media";
-    field public static final String NAVIGATION = "navigation";
-    field public static final String NONE = "none";
-    field public static final String OTHER = "other";
-    field public static final String PLAYBACK = "playback";
-    field public static final String PREDICTIVE_BACK = "predictive_back";
-    field public static final String SCROLL = "scroll";
-    field public static final String SCROLLING = "scrolling";
-    field public static final String SWIPING = "swiping";
-    field public static final String TAPPING = "tapping";
-    field public static final String WIDGET_CATEGORY_UNSPECIFIED = "widget_category_unspecified";
-    field public static final String WIDGET_STATE_UNSPECIFIED = "widget_state_unspecified";
-    field public static final String ZOOMING = "zooming";
+    field public static final String WIDGET_CATEGORY_ANIMATION = "animation";
+    field public static final String WIDGET_CATEGORY_KEYBOARD = "keyboard";
+    field public static final String WIDGET_CATEGORY_MEDIA = "media";
+    field public static final String WIDGET_CATEGORY_NAVIGATION = "navigation";
+    field public static final String WIDGET_CATEGORY_OTHER = "other";
+    field public static final String WIDGET_CATEGORY_SCROLL = "scroll";
+    field public static final String WIDGET_CATEGORY_UNSPECIFIED = "unspecified";
+    field public static final String WIDGET_STATE_ANIMATING = "animating";
+    field public static final String WIDGET_STATE_DRAGGING = "dragging";
+    field public static final String WIDGET_STATE_FLINGING = "flinging";
+    field public static final String WIDGET_STATE_NONE = "none";
+    field public static final String WIDGET_STATE_PLAYBACK = "playback";
+    field public static final String WIDGET_STATE_PREDICTIVE_BACK = "predictive_back";
+    field public static final String WIDGET_STATE_SCROLLING = "scrolling";
+    field public static final String WIDGET_STATE_SWIPING = "swiping";
+    field public static final String WIDGET_STATE_TAPPING = "tapping";
+    field public static final String WIDGET_STATE_UNSPECIFIED = "unspecified";
+    field public static final String WIDGET_STATE_ZOOMING = "zooming";
   }
 
-  @FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public class FrameOverrunHistogram {
-    ctor public FrameOverrunHistogram();
-    method public void addFrameOverrunMillis(int);
+  @FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public class RelativeFrameTimeHistogram {
+    ctor public RelativeFrameTimeHistogram();
+    method public void addRelativeFrameTimeMillis(int);
     method @NonNull public int[] getBucketCounters();
     method @NonNull public int[] getBucketEndpointsMillis();
   }
@@ -42521,7 +42521,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.GetValueRequest> CREATOR;
   }
 
-  public static final class GetValueRequest.Builder {
+  @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class GetValueRequest.Builder {
     ctor public GetValueRequest.Builder(@NonNull String, @NonNull String);
     method @NonNull public android.service.settings.preferences.GetValueRequest build();
   }
@@ -42542,7 +42542,7 @@
     field public static final int RESULT_UNSUPPORTED = 1; // 0x1
   }
 
-  public static final class GetValueResult.Builder {
+  @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class GetValueResult.Builder {
     ctor public GetValueResult.Builder(int);
     method @NonNull public android.service.settings.preferences.GetValueResult build();
     method @NonNull public android.service.settings.preferences.GetValueResult.Builder setMetadata(@Nullable android.service.settings.preferences.SettingsPreferenceMetadata);
@@ -42555,7 +42555,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.MetadataRequest> CREATOR;
   }
 
-  public static final class MetadataRequest.Builder {
+  @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class MetadataRequest.Builder {
     ctor public MetadataRequest.Builder();
     method @NonNull public android.service.settings.preferences.MetadataRequest build();
   }
@@ -42571,7 +42571,7 @@
     field public static final int RESULT_UNSUPPORTED = 1; // 0x1
   }
 
-  public static final class MetadataResult.Builder {
+  @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class MetadataResult.Builder {
     ctor public MetadataResult.Builder(int);
     method @NonNull public android.service.settings.preferences.MetadataResult build();
     method @NonNull public android.service.settings.preferences.MetadataResult.Builder setMetadataList(@NonNull java.util.List<android.service.settings.preferences.SettingsPreferenceMetadata>);
@@ -42586,7 +42586,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SetValueRequest> CREATOR;
   }
 
-  public static final class SetValueRequest.Builder {
+  @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class SetValueRequest.Builder {
     ctor public SetValueRequest.Builder(@NonNull String, @NonNull String, @NonNull android.service.settings.preferences.SettingsPreferenceValue);
     method @NonNull public android.service.settings.preferences.SetValueRequest build();
   }
@@ -42608,14 +42608,13 @@
     field public static final int RESULT_UNSUPPORTED = 1; // 0x1
   }
 
-  public static final class SetValueResult.Builder {
+  @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class SetValueResult.Builder {
     ctor public SetValueResult.Builder(int);
     method @NonNull public android.service.settings.preferences.SetValueResult build();
   }
 
   @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SettingsPreferenceMetadata implements android.os.Parcelable {
     method public int describeContents();
-    method @NonNull public java.util.List<java.lang.String> getBreadcrumbs();
     method @NonNull public android.os.Bundle getExtras();
     method @NonNull public String getKey();
     method @Nullable public android.content.Intent getLaunchIntent();
@@ -42631,17 +42630,16 @@
     method public boolean isWritable();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SettingsPreferenceMetadata> CREATOR;
+    field public static final int DEEPLINK_ONLY = 2; // 0x2
     field public static final int EXPECT_POST_CONFIRMATION = 1; // 0x1
-    field public static final int EXPECT_PRE_CONFIRMATION = 2; // 0x2
     field public static final int NO_DIRECT_ACCESS = 3; // 0x3
     field public static final int NO_SENSITIVITY = 0; // 0x0
   }
 
-  public static final class SettingsPreferenceMetadata.Builder {
+  @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class SettingsPreferenceMetadata.Builder {
     ctor public SettingsPreferenceMetadata.Builder(@NonNull String, @NonNull String);
     method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata build();
     method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setAvailable(boolean);
-    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setBreadcrumbs(@NonNull java.util.List<java.lang.String>);
     method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setEnabled(boolean);
     method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setExtras(@NonNull android.os.Bundle);
     method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setLaunchIntent(@Nullable android.content.Intent);
@@ -42688,7 +42686,7 @@
     field public static final int TYPE_STRING = 3; // 0x3
   }
 
-  public static final class SettingsPreferenceValue.Builder {
+  @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class SettingsPreferenceValue.Builder {
     ctor public SettingsPreferenceValue.Builder(int);
     method @NonNull public android.service.settings.preferences.SettingsPreferenceValue build();
     method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setBooleanValue(boolean);
@@ -53484,9 +53482,9 @@
     field public static final int CHANGE_FRAME_RATE_ALWAYS = 1; // 0x1
     field public static final int CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS = 0; // 0x0
     field @NonNull public static final android.os.Parcelable.Creator<android.view.Surface> CREATOR;
+    field @FlaggedApi("com.android.graphics.surfaceflinger.flags.arr_setframerate_gte_enum") public static final int FRAME_RATE_COMPATIBILITY_AT_LEAST = 2; // 0x2
     field public static final int FRAME_RATE_COMPATIBILITY_DEFAULT = 0; // 0x0
     field public static final int FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1; // 0x1
-    field @FlaggedApi("com.android.graphics.surfaceflinger.flags.arr_setframerate_gte_enum") public static final int FRAME_RATE_COMPATIBILITY_GTE = 2; // 0x2
     field public static final int ROTATION_0 = 0; // 0x0
     field public static final int ROTATION_180 = 2; // 0x2
     field public static final int ROTATION_270 = 3; // 0x3
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d95a17f5..d5cb6c0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -5079,8 +5079,8 @@
   }
 
   @FlaggedApi("android.chre.flags.offload_api") public class HubEndpoint {
-    method @Nullable public android.hardware.contexthub.IHubEndpointLifecycleCallback getLifecycleCallback();
-    method @Nullable public android.hardware.contexthub.IHubEndpointMessageCallback getMessageCallback();
+    method @Nullable public android.hardware.contexthub.HubEndpointLifecycleCallback getLifecycleCallback();
+    method @Nullable public android.hardware.contexthub.HubEndpointMessageCallback getMessageCallback();
     method @NonNull public java.util.Collection<android.hardware.contexthub.HubServiceInfo> getServiceInfoCollection();
     method @Nullable public String getTag();
     method public int getVersion();
@@ -5095,14 +5095,19 @@
   public static final class HubEndpoint.Builder {
     ctor public HubEndpoint.Builder(@NonNull android.content.Context);
     method @NonNull public android.hardware.contexthub.HubEndpoint build();
-    method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback);
-    method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback);
-    method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull android.hardware.contexthub.IHubEndpointMessageCallback);
-    method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.IHubEndpointMessageCallback);
+    method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull android.hardware.contexthub.HubEndpointLifecycleCallback);
+    method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.HubEndpointLifecycleCallback);
+    method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull android.hardware.contexthub.HubEndpointMessageCallback);
+    method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.HubEndpointMessageCallback);
     method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setServiceInfoCollection(@NonNull java.util.Collection<android.hardware.contexthub.HubServiceInfo>);
     method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setTag(@NonNull String);
   }
 
+  @FlaggedApi("android.chre.flags.offload_api") public interface HubEndpointDiscoveryCallback {
+    method public void onEndpointsStarted(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>);
+    method public void onEndpointsStopped(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>, int);
+  }
+
   @FlaggedApi("android.chre.flags.offload_api") public final class HubEndpointInfo implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.hardware.contexthub.HubEndpointInfo.HubEndpointIdentifier getIdentifier();
@@ -5126,6 +5131,16 @@
     method public long getHub();
   }
 
+  @FlaggedApi("android.chre.flags.offload_api") public interface HubEndpointLifecycleCallback {
+    method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int);
+    method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable String);
+    method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession);
+  }
+
+  @FlaggedApi("android.chre.flags.offload_api") public interface HubEndpointMessageCallback {
+    method public void onMessageReceived(@NonNull android.hardware.contexthub.HubEndpointSession, @NonNull android.hardware.contexthub.HubMessage);
+  }
+
   @FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSession implements java.lang.AutoCloseable {
     method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void close();
     method @Nullable public String getServiceDescriptor();
@@ -5174,21 +5189,6 @@
     method @NonNull public android.hardware.contexthub.HubServiceInfo build();
   }
 
-  @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointDiscoveryCallback {
-    method public void onEndpointsStarted(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>);
-    method public void onEndpointsStopped(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>, int);
-  }
-
-  @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointLifecycleCallback {
-    method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int);
-    method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable String);
-    method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession);
-  }
-
-  @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointMessageCallback {
-    method public void onMessageReceived(@NonNull android.hardware.contexthub.HubEndpointSession, @NonNull android.hardware.contexthub.HubMessage);
-  }
-
 }
 
 package android.hardware.devicestate {
@@ -6192,16 +6192,16 @@
     method @Deprecated public int registerCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
     method @Deprecated public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler);
     method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpoint(@NonNull android.hardware.contexthub.HubEndpoint);
-    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(long, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback);
-    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(long, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback, @NonNull java.util.concurrent.Executor);
-    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull String, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback);
-    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull String, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback, @NonNull java.util.concurrent.Executor);
+    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback, long);
+    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback, long);
+    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback, @NonNull String);
+    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback, @NonNull String);
     method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int sendMessage(int, int, @NonNull android.hardware.location.ContextHubMessage);
     method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int unloadNanoApp(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> unloadNanoApp(@NonNull android.hardware.location.ContextHubInfo, long);
     method @Deprecated public int unregisterCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
     method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpoint(@NonNull android.hardware.contexthub.HubEndpoint);
-    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpointDiscoveryCallback(@NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback);
+    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpointDiscoveryCallback(@NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback);
     field public static final int AUTHORIZATION_DENIED = 0; // 0x0
     field public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1; // 0x1
     field public static final int AUTHORIZATION_GRANTED = 2; // 0x2
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index fee8cdb..c3ef104 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5834,7 +5834,11 @@
         final int size = permissions.length;
         int[] results = new int[size];
         for (int i = 0; i < size; i++) {
-            results[i] = deviceContext.getPermissionRequestState(permissions[i]);
+            if (permissions[i] == null) {
+                results[i] = Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE;
+            } else {
+                results[i] = deviceContext.getPermissionRequestState(permissions[i]);
+            }
         }
         return results;
     }
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index 61b5687..599f1a8 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -27,6 +27,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 
 /**
  * Stores App Compat information about a particular Task.
@@ -58,16 +59,11 @@
     public int topActivityLetterboxHeight = PROPERTY_VALUE_UNSET;
 
     /**
-     * Contains the current app height of the letterboxed activity if available or
-     * {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
+     * Contains the app bounds of the top activity or size compat mode
+     * bounds when in size compat mode. If null, contains bounds.
      */
-    public int topActivityLetterboxAppHeight = PROPERTY_VALUE_UNSET;
-
-    /**
-     * Contains the current app  width of the letterboxed activity if available or
-     * {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
-     */
-    public int topActivityLetterboxAppWidth = PROPERTY_VALUE_UNSET;
+    @NonNull
+    public final Rect topActivityAppBounds = new Rect();
 
     /**
      * Contains the top activity bounds when the activity is letterboxed.
@@ -350,8 +346,7 @@
                 && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition
                 && topActivityLetterboxWidth == that.topActivityLetterboxWidth
                 && topActivityLetterboxHeight == that.topActivityLetterboxHeight
-                && topActivityLetterboxAppWidth == that.topActivityLetterboxAppWidth
-                && topActivityLetterboxAppHeight == that.topActivityLetterboxAppHeight
+                && topActivityAppBounds.equals(that.topActivityAppBounds)
                 && topActivityLetterboxHorizontalPosition
                     == that.topActivityLetterboxHorizontalPosition
                 && cameraCompatTaskInfo.equalsForTaskOrganizer(that.cameraCompatTaskInfo);
@@ -371,8 +366,7 @@
                     == that.topActivityLetterboxHorizontalPosition
                 && topActivityLetterboxWidth == that.topActivityLetterboxWidth
                 && topActivityLetterboxHeight == that.topActivityLetterboxHeight
-                && topActivityLetterboxAppWidth == that.topActivityLetterboxAppWidth
-                && topActivityLetterboxAppHeight == that.topActivityLetterboxAppHeight
+                && topActivityAppBounds.equals(that.topActivityAppBounds)
                 && cameraCompatTaskInfo.equalsForCompatUi(that.cameraCompatTaskInfo);
     }
 
@@ -385,8 +379,7 @@
         topActivityLetterboxHorizontalPosition = source.readInt();
         topActivityLetterboxWidth = source.readInt();
         topActivityLetterboxHeight = source.readInt();
-        topActivityLetterboxAppWidth = source.readInt();
-        topActivityLetterboxAppHeight = source.readInt();
+        topActivityAppBounds.set(Objects.requireNonNull(source.readTypedObject(Rect.CREATOR)));
         topActivityLetterboxBounds = source.readTypedObject(Rect.CREATOR);
         cameraCompatTaskInfo = source.readTypedObject(CameraCompatTaskInfo.CREATOR);
     }
@@ -401,8 +394,7 @@
         dest.writeInt(topActivityLetterboxHorizontalPosition);
         dest.writeInt(topActivityLetterboxWidth);
         dest.writeInt(topActivityLetterboxHeight);
-        dest.writeInt(topActivityLetterboxAppWidth);
-        dest.writeInt(topActivityLetterboxAppHeight);
+        dest.writeTypedObject(topActivityAppBounds, flags);
         dest.writeTypedObject(topActivityLetterboxBounds, flags);
         dest.writeTypedObject(cameraCompatTaskInfo, flags);
     }
@@ -421,8 +413,7 @@
                 + topActivityLetterboxHorizontalPosition
                 + " topActivityLetterboxWidth=" + topActivityLetterboxWidth
                 + " topActivityLetterboxHeight=" + topActivityLetterboxHeight
-                + " topActivityLetterboxAppWidth=" + topActivityLetterboxAppWidth
-                + " topActivityLetterboxAppHeight=" + topActivityLetterboxAppHeight
+                + " topActivityAppBounds=" + topActivityAppBounds
                 + " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled()
                 + " isSystemFullscreenOverrideEnabled=" + isSystemFullscreenOverrideEnabled()
                 + " hasMinAspectRatioOverride=" + hasMinAspectRatioOverride()
diff --git a/core/java/android/app/IUserSwitchObserver.aidl b/core/java/android/app/IUserSwitchObserver.aidl
index 1ff7a17..d71ee7c 100644
--- a/core/java/android/app/IUserSwitchObserver.aidl
+++ b/core/java/android/app/IUserSwitchObserver.aidl
@@ -19,10 +19,10 @@
 import android.os.IRemoteCallback;
 
 /** {@hide} */
-interface IUserSwitchObserver {
-    void onBeforeUserSwitching(int newUserId);
-    oneway void onUserSwitching(int newUserId, IRemoteCallback reply);
-    oneway void onUserSwitchComplete(int newUserId);
-    oneway void onForegroundProfileSwitch(int newProfileId);
-    oneway void onLockedBootComplete(int newUserId);
+oneway interface IUserSwitchObserver {
+    void onBeforeUserSwitching(int newUserId, IRemoteCallback reply);
+    void onUserSwitching(int newUserId, IRemoteCallback reply);
+    void onUserSwitchComplete(int newUserId);
+    void onForegroundProfileSwitch(int newProfileId);
+    void onLockedBootComplete(int newUserId);
 }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 17638ee..eeb1ebb 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -11208,8 +11208,8 @@
         private static final String KEY_SEGMENT_LENGTH = "length";
         private static final String KEY_POINT_POSITION = "position";
 
-        private static final int MAX_PROGRESS_SEGMENT_LIMIT = 15;
-        private static final int MAX_PROGRESS_STOP_LIMIT = 5;
+        private static final int MAX_PROGRESS_SEGMENT_LIMIT = 10;
+        private static final int MAX_PROGRESS_POINT_LIMIT = 4;
         private static final int DEFAULT_PROGRESS_MAX = 100;
 
         private List<Segment> mProgressSegments = new ArrayList<>();
@@ -11286,7 +11286,9 @@
                 mProgressSegments = new ArrayList<>();
             }
             mProgressSegments.clear();
-            mProgressSegments.addAll(progressSegments);
+            for (Segment segment : progressSegments) {
+                addProgressSegment(segment);
+            }
             return this;
         }
 
@@ -11302,7 +11304,11 @@
             if (mProgressSegments == null) {
                 mProgressSegments = new ArrayList<>();
             }
-            mProgressSegments.add(segment);
+            if (segment.getLength() > 0) {
+                mProgressSegments.add(segment);
+            } else {
+                Log.w(TAG, "Dropped the segment. The length is not a positive integer.");
+            }
 
             return this;
         }
@@ -11327,7 +11333,14 @@
          * @see Point
          */
         public @NonNull ProgressStyle setProgressPoints(@NonNull List<Point> points) {
-            mProgressPoints = new ArrayList<>(points);
+            if (mProgressPoints == null) {
+                mProgressPoints = new ArrayList<>();
+            }
+            mProgressPoints.clear();
+
+            for (Point point: points) {
+                addProgressPoint(point);
+            }
             return this;
         }
 
@@ -11348,7 +11361,17 @@
             if (mProgressPoints == null) {
                 mProgressPoints = new ArrayList<>();
             }
-            mProgressPoints.add(point);
+            if (point.getPosition() >= 0) {
+                mProgressPoints.add(point);
+
+                if (mProgressPoints.size() > MAX_PROGRESS_POINT_LIMIT) {
+                    Log.w(TAG, "Progress points limit is reached. First"
+                            + MAX_PROGRESS_POINT_LIMIT + " points will be rendered.");
+                }
+
+            } else {
+                Log.w(TAG, "Dropped the point. The position is a negative integer.");
+            }
 
             return this;
         }
@@ -11384,8 +11407,7 @@
             } else {
                 int progressMax = 0;
                 int validSegmentCount = 0;
-                for (int i = 0; i < progressSegment.size()
-                        && validSegmentCount < MAX_PROGRESS_SEGMENT_LIMIT; i++) {
+                for (int i = 0; i < progressSegment.size(); i++) {
                     int segmentLength = progressSegment.get(i).getLength();
                     if (segmentLength > 0) {
                         try {
@@ -11832,6 +11854,30 @@
                     totalLength = DEFAULT_PROGRESS_MAX;
                     segments.add(sanitizeSegment(new Segment(totalLength), backgroundColor,
                             defaultProgressColor));
+                } else if (segments.size() > MAX_PROGRESS_SEGMENT_LIMIT) {
+                    // If segment limit is exceeded. All segments will be replaced
+                    // with a single segment
+                    boolean allSameColor = true;
+                    int firstSegmentColor = segments.get(0).getColor();
+
+                    for (int i = 1; i < segments.size(); i++) {
+                        if (segments.get(i).getColor() != firstSegmentColor) {
+                            allSameColor = false;
+                            break;
+                        }
+                    }
+
+                    // This single segment length has same max as total.
+                    final Segment singleSegment = new Segment(totalLength);
+                    // Single segment color: if all segments have the same color,
+                    // use that color. Otherwise, use 0 / default.
+                    singleSegment.setColor(allSameColor ? firstSegmentColor
+                            : Notification.COLOR_DEFAULT);
+
+                    segments.clear();
+                    segments.add(sanitizeSegment(singleSegment,
+                            backgroundColor,
+                            defaultProgressColor));
                 }
 
                 // Ensure point color contrasts.
@@ -11840,6 +11886,9 @@
                     final int position = point.getPosition();
                     if (position < 0 || position > totalLength) continue;
                     points.add(sanitizePoint(point, backgroundColor, defaultProgressColor));
+                    if (points.size() == MAX_PROGRESS_POINT_LIMIT) {
+                        break;
+                    }
                 }
 
                 model = new NotificationProgressModel(segments, points,
@@ -11868,8 +11917,10 @@
          * has the same hue as the original color, but is lightened or darkened depending on
          * whether the background is dark or light.
          *
+         * @hide
          */
-        private int sanitizeProgressColor(@ColorInt int color,
+        @VisibleForTesting
+        public static int sanitizeProgressColor(@ColorInt int color,
                 @ColorInt int bg,
                 @ColorInt int defaultColor) {
             return Builder.ensureColorContrast(
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 8ed66eb..e9b889a 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -55,6 +55,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.StrictMode;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.Global;
@@ -677,9 +678,14 @@
     }
 
     /** {@hide} */
-    @UnsupportedAppUsage
-    public NotificationManager(Context context, InstantSource clock)
+    public NotificationManager(Context context)
     {
+        this(context, SystemClock.elapsedRealtimeClock());
+    }
+
+    /** {@hide} */
+    @UnsupportedAppUsage
+    public NotificationManager(Context context, InstantSource clock) {
         mContext = context;
         mClock = clock;
     }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index cf9a59f..0bbe943 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -289,7 +289,6 @@
 import com.android.internal.policy.PhoneLayoutInflater;
 import com.android.internal.util.Preconditions;
 
-import java.time.InstantSource;
 import java.util.Map;
 import java.util.Objects;
 
@@ -625,8 +624,8 @@
                                     com.android.internal.R.style.Theme_Dialog,
                                     com.android.internal.R.style.Theme_Holo_Dialog,
                                     com.android.internal.R.style.Theme_DeviceDefault_Dialog,
-                                    com.android.internal.R.style.Theme_DeviceDefault_Light_Dialog)),
-                    InstantSource.system());
+                                    com.android.internal.R.style.Theme_DeviceDefault_Light_Dialog))
+                );
             }});
 
         registerService(Context.PEOPLE_SERVICE, PeopleManager.class,
diff --git a/core/java/android/app/UserSwitchObserver.java b/core/java/android/app/UserSwitchObserver.java
index 727799a1..1664cfb 100644
--- a/core/java/android/app/UserSwitchObserver.java
+++ b/core/java/android/app/UserSwitchObserver.java
@@ -30,7 +30,11 @@
     }
 
     @Override
-    public void onBeforeUserSwitching(int newUserId) throws RemoteException {}
+    public void onBeforeUserSwitching(int newUserId, IRemoteCallback reply) throws RemoteException {
+        if (reply != null) {
+            reply.sendResult(null);
+        }
+    }
 
     @Override
     public void onUserSwitching(int newUserId, IRemoteCallback reply) throws RemoteException {
diff --git a/core/java/android/app/contextualsearch/ContextualSearchManager.java b/core/java/android/app/contextualsearch/ContextualSearchManager.java
index 3438cc8..ad43f27 100644
--- a/core/java/android/app/contextualsearch/ContextualSearchManager.java
+++ b/core/java/android/app/contextualsearch/ContextualSearchManager.java
@@ -48,7 +48,9 @@
 
     /**
      * Key to get the entrypoint from the extras of the activity launched by contextual search.
-     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+     * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
+     *
+     * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
      */
     public static final String EXTRA_ENTRYPOINT =
             "android.app.contextualsearch.extra.ENTRYPOINT";
@@ -56,14 +58,18 @@
     /**
      * Key to get the flag_secure value from the extras of the activity launched by contextual
      * search. The value will be true if flag_secure is found in any of the visible activities.
-     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+     * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
+     *
+     * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
      */
     public static final String EXTRA_FLAG_SECURE_FOUND =
             "android.app.contextualsearch.extra.FLAG_SECURE_FOUND";
 
     /**
      * Key to get the screenshot from the extras of the activity launched by contextual search.
-     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+     * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
+     *
+     * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
      */
     public static final String EXTRA_SCREENSHOT =
             "android.app.contextualsearch.extra.SCREENSHOT";
@@ -71,7 +77,9 @@
     /**
      * Key to check whether managed profile is visible from the extras of the activity launched by
      * contextual search. The value will be true if any one of the visible apps is managed.
-     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+     * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
+     *
+     * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
      */
     public static final String EXTRA_IS_MANAGED_PROFILE_VISIBLE =
             "android.app.contextualsearch.extra.IS_MANAGED_PROFILE_VISIBLE";
@@ -79,7 +87,9 @@
     /**
      * Key to get the list of visible packages from the extras of the activity launched by
      * contextual search.
-     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+     * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
+     *
+     * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
      */
     public static final String EXTRA_VISIBLE_PACKAGE_NAMES =
             "android.app.contextualsearch.extra.VISIBLE_PACKAGE_NAMES";
@@ -87,7 +97,9 @@
     /**
      * Key to get the time the user made the invocation request, based on
      * {@link SystemClock#uptimeMillis()}.
-     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+     * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
+     *
+     * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
      *
      * TODO: un-hide in W
      *
@@ -99,11 +111,24 @@
     /**
      * Key to get the binder token from the extras of the activity launched by contextual search.
      * This token is needed to invoke {@link CallbackToken#getContextualSearchState} method.
-     * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+     * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
+     *
+     * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
      */
     public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN";
 
     /**
+     * Key to check whether audio is playing when contextual search is invoked.
+     * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
+     *
+     * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
+     *
+     * @hide
+     */
+    public static final String EXTRA_IS_AUDIO_PLAYING =
+            "android.app.contextualsearch.extra.IS_AUDIO_PLAYING";
+
+    /**
      * Intent action for contextual search invocation. The app providing the contextual search
      * experience must add this intent filter action to the activity it wants to be launched.
      * <br>
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index e8cfd79..c19921d 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -8,6 +8,7 @@
   bug: "309689654"
   is_exported: true
 }
+
 flag {
   name: "enable_token_refresh"
   namespace: "machine_learning"
@@ -27,4 +28,11 @@
     namespace: "sysui_integrations"
     description: "Identify live contextual search UI to exclude from contextual search screenshot."
     bug: "372510690"
+}
+
+flag {
+    name: "include_audio_playing_status"
+    namespace: "sysui_integrations"
+    description: "Add audio playing status to the contextual search invocation intent."
+    bug: "372935419"
 }
\ No newline at end of file
diff --git a/core/java/android/app/jank/AppJankStats.java b/core/java/android/app/jank/AppJankStats.java
index eea1d2b..6ef6a44 100644
--- a/core/java/android/app/jank/AppJankStats.java
+++ b/core/java/android/app/jank/AppJankStats.java
@@ -41,7 +41,8 @@
     // The id that has been set for the widget.
     private String mWidgetId;
 
-    // A general category that the widget applies to.
+    // A general category the widget falls into based on the functions it performs or helps
+    // facilitate.
     private String mWidgetCategory;
 
     // The states that the UI elements can report
@@ -53,78 +54,78 @@
     // Total number of frames determined to be janky during the reported state.
     private long mJankyFrames;
 
-    // Histogram of frame duration overruns encoded in predetermined buckets.
-    private FrameOverrunHistogram mFrameOverrunHistogram;
+    // Histogram of relative frame times encoded in predetermined buckets.
+    private RelativeFrameTimeHistogram mRelativeFrameTimeHistogram;
 
 
     /** Used to indicate no widget category has been set. */
-    public static final String WIDGET_CATEGORY_UNSPECIFIED =
-            "widget_category_unspecified";
+    public static final String WIDGET_CATEGORY_UNSPECIFIED = "unspecified";
 
     /** UI elements that facilitate scrolling. */
-    public static final String SCROLL = "scroll";
+    public static final String WIDGET_CATEGORY_SCROLL = "scroll";
 
     /** UI elements that facilitate playing animations. */
-    public static final String ANIMATION = "animation";
+    public static final String WIDGET_CATEGORY_ANIMATION = "animation";
 
     /** UI elements that facilitate media playback. */
-    public static final String MEDIA = "media";
+    public static final String WIDGET_CATEGORY_MEDIA = "media";
 
     /** UI elements that facilitate in-app navigation. */
-    public static final String NAVIGATION = "navigation";
+    public static final String WIDGET_CATEGORY_NAVIGATION = "navigation";
 
     /** UI elements that facilitate displaying, hiding or interacting with keyboard. */
-    public static final String KEYBOARD = "keyboard";
-
-    /** UI elements that facilitate predictive back gesture navigation. */
-    public static final String PREDICTIVE_BACK = "predictive_back";
+    public static final String WIDGET_CATEGORY_KEYBOARD = "keyboard";
 
     /** UI elements that don't fall in one or any of the other categories. */
-    public static final String OTHER = "other";
+    public static final String WIDGET_CATEGORY_OTHER = "other";
 
     /** Used to indicate no widget state has been set. */
-    public static final String WIDGET_STATE_UNSPECIFIED = "widget_state_unspecified";
+    public static final String WIDGET_STATE_UNSPECIFIED = "unspecified";
 
     /** Used to indicate the UI element currently has no state and is idle. */
-    public static final String NONE = "none";
+    public static final String WIDGET_STATE_NONE = "none";
 
     /** Used to indicate the UI element is currently scrolling. */
-    public static final String SCROLLING = "scrolling";
+    public static final String WIDGET_STATE_SCROLLING = "scrolling";
 
     /** Used to indicate the UI element is currently being flung. */
-    public static final String FLINGING = "flinging";
+    public static final String WIDGET_STATE_FLINGING = "flinging";
 
     /** Used to indicate the UI element is currently being swiped. */
-    public static final String SWIPING = "swiping";
+    public static final String WIDGET_STATE_SWIPING = "swiping";
 
     /** Used to indicate the UI element is currently being dragged. */
-    public static final String DRAGGING = "dragging";
+    public static final String WIDGET_STATE_DRAGGING = "dragging";
 
     /** Used to indicate the UI element is currently zooming. */
-    public static final String ZOOMING = "zooming";
+    public static final String WIDGET_STATE_ZOOMING = "zooming";
 
     /** Used to indicate the UI element is currently animating. */
-    public static final String ANIMATING = "animating";
+    public static final String WIDGET_STATE_ANIMATING = "animating";
 
     /** Used to indicate the UI element is currently playing media. */
-    public static final String PLAYBACK = "playback";
+    public static final String WIDGET_STATE_PLAYBACK = "playback";
 
     /** Used to indicate the UI element is currently being tapped on, for example on a keyboard. */
-    public static final String TAPPING = "tapping";
+    public static final String WIDGET_STATE_TAPPING = "tapping";
+
+    /** Used to indicate predictive back navigation is currently being used */
+    public static final String WIDGET_STATE_PREDICTIVE_BACK = "predictive_back";
 
 
     /**
+     * Provide an organized way to group widgets that have similar purposes or perform related
+     * functions.
      * @hide
      */
-    @StringDef(value = {
+    @StringDef(prefix = {"WIDGET_CATEGORY_"}, value = {
             WIDGET_CATEGORY_UNSPECIFIED,
-            SCROLL,
-            ANIMATION,
-            MEDIA,
-            NAVIGATION,
-            KEYBOARD,
-            PREDICTIVE_BACK,
-            OTHER
+            WIDGET_CATEGORY_SCROLL,
+            WIDGET_CATEGORY_ANIMATION,
+            WIDGET_CATEGORY_MEDIA,
+            WIDGET_CATEGORY_NAVIGATION,
+            WIDGET_CATEGORY_KEYBOARD,
+            WIDGET_CATEGORY_OTHER
     })
     @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
     @Retention(RetentionPolicy.SOURCE)
@@ -133,17 +134,18 @@
     /**
      * @hide
      */
-    @StringDef(value = {
+    @StringDef(prefix = {"WIDGET_STATE_"}, value = {
             WIDGET_STATE_UNSPECIFIED,
-            NONE,
-            SCROLLING,
-            FLINGING,
-            SWIPING,
-            DRAGGING,
-            ZOOMING,
-            ANIMATING,
-            PLAYBACK,
-            TAPPING,
+            WIDGET_STATE_NONE,
+            WIDGET_STATE_SCROLLING,
+            WIDGET_STATE_FLINGING,
+            WIDGET_STATE_SWIPING,
+            WIDGET_STATE_DRAGGING,
+            WIDGET_STATE_ZOOMING,
+            WIDGET_STATE_ANIMATING,
+            WIDGET_STATE_PLAYBACK,
+            WIDGET_STATE_TAPPING,
+            WIDGET_STATE_PREDICTIVE_BACK
     })
     @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
     @Retention(RetentionPolicy.SOURCE)
@@ -156,31 +158,33 @@
      *
      * @param appUid the Uid of the App that is collecting jank stats.
      * @param widgetId the widget id that frames will be associated to.
-     * @param widgetCategory a general functionality category that the widget falls into. Must be
-     *                       one of the following: SCROLL, ANIMATION, MEDIA, NAVIGATION, KEYBOARD,
-     *                       PREDICTIVE_BACK, OTHER or will be set to WIDGET_CATEGORY_UNSPECIFIED
-     *                       if no value is passed.
-     * @param widgetState the state the widget was in while frames were counted. Must be one of
-     *                    the following: NONE, SCROLLING, FLINGING, SWIPING, DRAGGING, ZOOMING,
-     *                    ANIMATING, PLAYBACK, TAPPING or will be set to WIDGET_STATE_UNSPECIFIED
-     *                    if no value is passed.
+     * @param widgetCategory a category used to organize widgets in a structured way that indicates
+     *                       they serve a similar purpose or perform related functions. Must be
+     *                       prefixed with WIDGET_CATEGORY_ and have a suffix of one of the
+     *                       following:SCROLL, ANIMATION, MEDIA, NAVIGATION, KEYBOARD, OTHER or
+     *                       will be set to UNSPECIFIED if no value is passed.
+     * @param widgetState the state the widget was in while frames were counted. Must be prefixed
+     *                    with WIDGET_STATE_ and have a suffix of one of the following:
+     *                    NONE, SCROLLING, FLINGING, SWIPING, DRAGGING, ZOOMING, ANIMATING,
+     *                    PLAYBACK, TAPPING, PREDICTIVE_BACK or will be set to
+     *                    WIDGET_STATE_UNSPECIFIED if no value is passed.
      * @param totalFrames the total number of frames that were counted for this stat.
      * @param jankyFrames the total number of janky frames that were counted for this stat.
-     * @param frameOverrunHistogram the histogram with predefined buckets. See
-     * {@link #getFrameOverrunHistogram()} for details.
+     * @param relativeFrameTimeHistogram the histogram with predefined buckets. See
+     * {@link #getRelativeFrameTimeHistogram()} for details.
      *
      */
     public AppJankStats(int appUid, @NonNull String widgetId,
             @Nullable @WidgetCategory String widgetCategory,
             @Nullable @WidgetState String widgetState, long totalFrames, long jankyFrames,
-            @NonNull FrameOverrunHistogram frameOverrunHistogram) {
+            @NonNull RelativeFrameTimeHistogram relativeFrameTimeHistogram) {
         mUid = appUid;
         mWidgetId = widgetId;
         mWidgetCategory = widgetCategory != null ? widgetCategory : WIDGET_CATEGORY_UNSPECIFIED;
         mWidgetState = widgetState != null ? widgetState : WIDGET_STATE_UNSPECIFIED;
         mTotalFrames = totalFrames;
         mJankyFrames = jankyFrames;
-        mFrameOverrunHistogram = frameOverrunHistogram;
+        mRelativeFrameTimeHistogram = relativeFrameTimeHistogram;
     }
 
     /**
@@ -203,7 +207,7 @@
 
     /**
      * Returns the category that the widget's functionality generally falls into, or
-     * widget_category_unspecified {@link #WIDGET_CATEGORY_UNSPECIFIED} if no value was passed in.
+     * {@link #WIDGET_CATEGORY_UNSPECIFIED} if no value was passed in.
      *
      * @return the category that the widget's functionality generally falls into, this value cannot
      * be null.
@@ -213,7 +217,7 @@
     }
 
     /**
-     * Returns the widget's state that was reported for this stat, or widget_state_unspecified
+     * Returns the widget's state that was reported for this stat, or
      * {@link #WIDGET_STATE_UNSPECIFIED} if no value was passed in.
      *
      * @return the widget's state that was reported for this stat. This value cannot be null.
@@ -241,13 +245,13 @@
     }
 
     /**
-     * Returns a Histogram containing frame overrun times in millis grouped into predefined buckets.
-     * See {@link FrameOverrunHistogram} for more information.
+     * Returns a Histogram containing relative frame times in millis grouped into predefined
+     * buckets. See {@link RelativeFrameTimeHistogram} for more information.
      *
-     * @return Histogram containing frame overrun times in predefined buckets. This value cannot
+     * @return Histogram containing relative frame times in predefined buckets. This value cannot
      * be null.
      */
-    public @NonNull FrameOverrunHistogram getFrameOverrunHistogram() {
-        return mFrameOverrunHistogram;
+    public @NonNull RelativeFrameTimeHistogram getRelativeFrameTimeHistogram() {
+        return mRelativeFrameTimeHistogram;
     }
 }
diff --git a/core/java/android/app/jank/FrameOverrunHistogram.java b/core/java/android/app/jank/FrameOverrunHistogram.java
deleted file mode 100644
index 3ad6531..0000000
--- a/core/java/android/app/jank/FrameOverrunHistogram.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.jank;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-
-import java.util.Arrays;
-
-/**
- * This class is intended to be used when reporting {@link AppJankStats} back to the system. It's
- * intended to be used by library widgets to help facilitate the reporting of frame overrun times
- * by adding those times into predefined buckets.
- */
-@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
-public class FrameOverrunHistogram {
-    private static int[] sBucketEndpoints = new int[]{
-            Integer.MIN_VALUE, -200, -150, -100, -90, -80, -70, -60, -50, -40, -30, -25, -20, -18,
-            -16, -14, -12, -10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 25, 30, 40,
-            50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000
-    };
-    private int[] mBucketCounts;
-
-    /**
-     * Create a new instance of FrameOverrunHistogram.
-     */
-    public FrameOverrunHistogram() {
-        mBucketCounts = new int[sBucketEndpoints.length];
-    }
-
-    /**
-     * Increases the count by one for the bucket representing the frame overrun duration.
-     *
-     * @param frameOverrunMillis frame overrun duration in millis, frame overrun is the difference
-     *                           between a frames deadline and when it was rendered.
-     */
-    public void addFrameOverrunMillis(int frameOverrunMillis) {
-        int countsIndex = getIndexForCountsFromOverrunTime(frameOverrunMillis);
-        mBucketCounts[countsIndex]++;
-    }
-
-    /**
-     * Returns the counts for the all the frame overrun buckets.
-     *
-     * @return an array of integers representing the counts of frame overrun times. This value
-     * cannot be null.
-     */
-    public @NonNull int[] getBucketCounters() {
-        return Arrays.copyOf(mBucketCounts, mBucketCounts.length);
-    }
-
-    /**
-     * Returns the predefined endpoints for the histogram.
-     *
-     * @return array of integers representing the endpoints for the predefined histogram count
-     * buckets. This value cannot be null.
-     */
-    public @NonNull int[] getBucketEndpointsMillis() {
-        return Arrays.copyOf(sBucketEndpoints, sBucketEndpoints.length);
-    }
-
-    // This takes the overrun time and returns what bucket it belongs to in the counters array.
-    private int getIndexForCountsFromOverrunTime(int overrunTime) {
-        if (overrunTime < 20) {
-            if (overrunTime >= -20) {
-                return (overrunTime + 20) / 2 + 12;
-            }
-            if (overrunTime >= -30) {
-                return (overrunTime + 30) / 5 + 10;
-            }
-            if (overrunTime >= -100) {
-                return (overrunTime + 100) / 10 + 3;
-            }
-            if (overrunTime >= -200) {
-                return (overrunTime + 200) / 50 + 1;
-            }
-            return 0;
-        }
-        if (overrunTime < 30) {
-            return (overrunTime - 20) / 5 + 32;
-        }
-        if (overrunTime < 100) {
-            return (overrunTime - 30) / 10 + 34;
-        }
-        if (overrunTime < 200) {
-            return (overrunTime - 50) / 100 + 41;
-        }
-        if (overrunTime < 1000) {
-            return (overrunTime - 200) / 100 + 43;
-        }
-        return sBucketEndpoints.length - 1;
-    }
-}
diff --git a/core/java/android/app/jank/JankDataProcessor.java b/core/java/android/app/jank/JankDataProcessor.java
index c947259..b4c293e 100644
--- a/core/java/android/app/jank/JankDataProcessor.java
+++ b/core/java/android/app/jank/JankDataProcessor.java
@@ -111,7 +111,7 @@
         pendingStat.mTotalFrames += jankStat.getTotalFrameCount();
 
         mergeOverrunHistograms(pendingStat.mFrameOverrunBuckets,
-                jankStat.getFrameOverrunHistogram().getBucketCounters());
+                jankStat.getRelativeFrameTimeHistogram().getBucketCounters());
     }
 
     private void mergeNewStat(String stateKey, String activityName, AppJankStats jankStats) {
@@ -136,7 +136,7 @@
         pendingStat.mJankyFrames = jankStats.getJankyFrameCount();
 
         mergeOverrunHistograms(pendingStat.mFrameOverrunBuckets,
-                jankStats.getFrameOverrunHistogram().getBucketCounters());
+                jankStats.getRelativeFrameTimeHistogram().getBucketCounters());
 
         mPendingJankStats.put(stateKey, pendingStat);
     }
@@ -271,7 +271,8 @@
         private static final int[] sFrameOverrunHistogramBounds =  {
                 Integer.MIN_VALUE, -200, -150, -100, -90, -80, -70, -60, -50, -40, -30, -25, -20,
                 -18, -16, -14, -12, -10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 25,
-                30, 40, 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000
+                30, 40, 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000,
+                Integer.MAX_VALUE
         };
         private final int[] mFrameOverrunBuckets = new int[sFrameOverrunHistogramBounds.length];
 
@@ -414,7 +415,7 @@
             if (overrunTime < 200) {
                 return (overrunTime - 50) / 100 + 41;
             }
-            if (overrunTime < 1000) {
+            if (overrunTime <= 1000) {
                 return (overrunTime - 200) / 100 + 43;
             }
             return sFrameOverrunHistogramBounds.length - 1;
diff --git a/core/java/android/app/jank/RelativeFrameTimeHistogram.java b/core/java/android/app/jank/RelativeFrameTimeHistogram.java
new file mode 100644
index 0000000..666f90f
--- /dev/null
+++ b/core/java/android/app/jank/RelativeFrameTimeHistogram.java
@@ -0,0 +1,129 @@
+/*
+ * 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.jank;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import java.util.Arrays;
+
+/**
+ * A histogram of frame times relative to their deadline.
+ *
+ * This class aids in reporting {@link AppJankStats} to the system and is designed for use by
+ * library widgets. It facilitates the recording of frame times in relation to the frame deadline.
+ * The class records the distribution of time remaining until a frame is considered janky or how
+ * janky the frame was.
+ * <p>
+ * A frame's relative frame time value indicates whether it was delivered early, on time, or late.
+ * A negative relative frame time value indicates the frame was delivered early, a value of zero
+ * indicates the frame was delivered on time and a positive value indicates the frame was delivered
+ * late. The values of the endpoints indicate how early or late a frame was delivered.
+ * <p>
+ * The relative frame times are recorded as a histogram: values are
+ * {@link #addRelativeFrameTimeMillis added} to a bucket by increasing the bucket's counter. The
+ * count of frames with a relative frame time between
+ * {@link #getBucketEndpointsMillis bucket endpoints} {@code i} and {@code i+1} can be obtained
+ * through index {@code i} of {@link #getBucketCounters}.
+ *
+ */
+@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+public class RelativeFrameTimeHistogram {
+    private static int[] sBucketEndpoints = new int[]{
+            Integer.MIN_VALUE, -200, -150, -100, -90, -80, -70, -60, -50, -40, -30, -25, -20, -18,
+            -16, -14, -12, -10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 25, 30, 40,
+            50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000,
+            Integer.MAX_VALUE
+    };
+    //
+    private int[] mBucketCounts;
+
+    /**
+     * Create a new instance of RelativeFrameTimeHistogram.
+     */
+    public RelativeFrameTimeHistogram() {
+        mBucketCounts = new int[sBucketEndpoints.length - 1];
+    }
+
+    /**
+     * Increases the count by one for the bucket representing the relative frame time.
+     *
+     * @param frameTimeMillis relative frame time in millis, relative frame time is the difference
+     *                           between a frames deadline and when it was rendered.
+     */
+    public void addRelativeFrameTimeMillis(int frameTimeMillis) {
+        int countsIndex = getRelativeFrameTimeBucketIndex(frameTimeMillis);
+        mBucketCounts[countsIndex]++;
+    }
+
+    /**
+     * Returns the counts for the all the relative frame time buckets.
+     *
+     * @return an array of integers representing the counts of relative frame times. This value
+     * cannot be null.
+     */
+    public @NonNull int[] getBucketCounters() {
+        return Arrays.copyOf(mBucketCounts, mBucketCounts.length);
+    }
+
+    /**
+     * Returns the relative frame time endpoints for the histogram.
+     * <p>
+     * Index {@code i} of {@link #getBucketCounters} contains the count of frames that had a
+     * relative frame time between {@code endpoints[i]} (inclusive) and {@code endpoints[i+1]}
+     * (exclusive).
+     *
+     * @return array of integers representing the endpoints for the predefined histogram count
+     * buckets. This value cannot be null.
+     */
+    public @NonNull int[] getBucketEndpointsMillis() {
+        return Arrays.copyOf(sBucketEndpoints, sBucketEndpoints.length);
+    }
+
+    // This takes the relative frame time and returns what bucket it belongs to in the counters
+    // array.
+    private int getRelativeFrameTimeBucketIndex(int relativeFrameTime) {
+        if (relativeFrameTime < 20) {
+            if (relativeFrameTime >= -20) {
+                return (relativeFrameTime + 20) / 2 + 12;
+            }
+            if (relativeFrameTime >= -30) {
+                return (relativeFrameTime + 30) / 5 + 10;
+            }
+            if (relativeFrameTime >= -100) {
+                return (relativeFrameTime + 100) / 10 + 3;
+            }
+            if (relativeFrameTime >= -200) {
+                return (relativeFrameTime + 200) / 50 + 1;
+            }
+            return 0;
+        }
+        if (relativeFrameTime < 30) {
+            return (relativeFrameTime - 20) / 5 + 32;
+        }
+        if (relativeFrameTime < 100) {
+            return (relativeFrameTime - 30) / 10 + 34;
+        }
+        if (relativeFrameTime < 200) {
+            return (relativeFrameTime - 50) / 100 + 41;
+        }
+        if (relativeFrameTime < 1000) {
+            return (relativeFrameTime - 200) / 100 + 43;
+        }
+        return mBucketCounts.length - 1;
+    }
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 34c0f7b..321f09b 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -482,8 +482,19 @@
 
             mRemoteDevice = new ICameraDeviceUserWrapper(remoteDevice);
             Parcel resultParcel = Parcel.obtain();
-            mRemoteDevice.getCaptureResultMetadataQueue().writeToParcel(resultParcel, 0);
+
+            // Passing in PARCELABLE_WRITE_RETURN_VALUE closes the ParcelFileDescriptors
+            // owned by MQDescriptor returned by getCaptureResultMetadataQueue()
+            // Though these will be closed when GC runs, that may not happen for a while.
+            // Also, apps running with StrictMode would get warnings / crash in the case they're not
+            // explicitly closed.
+            mRemoteDevice.getCaptureResultMetadataQueue().writeToParcel(resultParcel,
+                    Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
             mFMQReader = nativeCreateFMQReader(resultParcel);
+            // Recycle since resultParcel would dup fds from MQDescriptor as well. We don't
+            // need them after the native FMQ reader has been created. That is since the native
+            // creates calls MQDescriptor.readFromParcel() which again dups the fds.
+            resultParcel.recycle();
 
             IBinder remoteDeviceBinder = remoteDevice.asBinder();
             // For legacy camera device, remoteDevice is in the same process, and
diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java
index 99f331f..1e5bed5 100644
--- a/core/java/android/hardware/contexthub/HubEndpoint.java
+++ b/core/java/android/hardware/contexthub/HubEndpoint.java
@@ -99,8 +99,8 @@
 
     private final Object mLock = new Object();
     private final HubEndpointInfo mPendingHubEndpointInfo;
-    @Nullable private final IHubEndpointLifecycleCallback mLifecycleCallback;
-    @Nullable private final IHubEndpointMessageCallback mMessageCallback;
+    @Nullable private final HubEndpointLifecycleCallback mLifecycleCallback;
+    @Nullable private final HubEndpointMessageCallback mMessageCallback;
     @NonNull private final Executor mLifecycleCallbackExecutor;
     @NonNull private final Executor mMessageCallbackExecutor;
 
@@ -335,9 +335,9 @@
 
     private HubEndpoint(
             @NonNull HubEndpointInfo pendingEndpointInfo,
-            @Nullable IHubEndpointLifecycleCallback endpointLifecycleCallback,
+            @Nullable HubEndpointLifecycleCallback endpointLifecycleCallback,
             @NonNull Executor lifecycleCallbackExecutor,
-            @Nullable IHubEndpointMessageCallback endpointMessageCallback,
+            @Nullable HubEndpointMessageCallback endpointMessageCallback,
             @NonNull Executor messageCallbackExecutor) {
         mPendingHubEndpointInfo = pendingEndpointInfo;
         mLifecycleCallback = endpointLifecycleCallback;
@@ -485,12 +485,12 @@
     }
 
     @Nullable
-    public IHubEndpointLifecycleCallback getLifecycleCallback() {
+    public HubEndpointLifecycleCallback getLifecycleCallback() {
         return mLifecycleCallback;
     }
 
     @Nullable
-    public IHubEndpointMessageCallback getMessageCallback() {
+    public HubEndpointMessageCallback getMessageCallback() {
         return mMessageCallback;
     }
 
@@ -498,11 +498,11 @@
     public static final class Builder {
         private final String mPackageName;
 
-        @Nullable private IHubEndpointLifecycleCallback mLifecycleCallback;
+        @Nullable private HubEndpointLifecycleCallback mLifecycleCallback;
 
         @NonNull private Executor mLifecycleCallbackExecutor;
 
-        @Nullable private IHubEndpointMessageCallback mMessageCallback;
+        @Nullable private HubEndpointMessageCallback mMessageCallback;
         @NonNull private Executor mMessageCallbackExecutor;
 
         private int mVersion;
@@ -542,7 +542,7 @@
         /** Attach a callback interface for lifecycle events for this Endpoint */
         @NonNull
         public Builder setLifecycleCallback(
-                @NonNull IHubEndpointLifecycleCallback lifecycleCallback) {
+                @NonNull HubEndpointLifecycleCallback lifecycleCallback) {
             mLifecycleCallback = lifecycleCallback;
             return this;
         }
@@ -554,7 +554,7 @@
         @NonNull
         public Builder setLifecycleCallback(
                 @NonNull @CallbackExecutor Executor executor,
-                @NonNull IHubEndpointLifecycleCallback lifecycleCallback) {
+                @NonNull HubEndpointLifecycleCallback lifecycleCallback) {
             mLifecycleCallbackExecutor = executor;
             mLifecycleCallback = lifecycleCallback;
             return this;
@@ -562,7 +562,7 @@
 
         /** Attach a callback interface for message events for this Endpoint */
         @NonNull
-        public Builder setMessageCallback(@NonNull IHubEndpointMessageCallback messageCallback) {
+        public Builder setMessageCallback(@NonNull HubEndpointMessageCallback messageCallback) {
             mMessageCallback = messageCallback;
             return this;
         }
@@ -574,7 +574,7 @@
         @NonNull
         public Builder setMessageCallback(
                 @NonNull @CallbackExecutor Executor executor,
-                @NonNull IHubEndpointMessageCallback messageCallback) {
+                @NonNull HubEndpointMessageCallback messageCallback) {
             mMessageCallbackExecutor = executor;
             mMessageCallback = messageCallback;
             return this;
diff --git a/core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java b/core/java/android/hardware/contexthub/HubEndpointDiscoveryCallback.java
similarity index 96%
rename from core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java
rename to core/java/android/hardware/contexthub/HubEndpointDiscoveryCallback.java
index a61a7eb..4672bbb 100644
--- a/core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java
+++ b/core/java/android/hardware/contexthub/HubEndpointDiscoveryCallback.java
@@ -30,7 +30,7 @@
  */
 @SystemApi
 @FlaggedApi(Flags.FLAG_OFFLOAD_API)
-public interface IHubEndpointDiscoveryCallback {
+public interface HubEndpointDiscoveryCallback {
     /**
      * Called when a list of hub endpoints have started.
      *
diff --git a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java b/core/java/android/hardware/contexthub/HubEndpointLifecycleCallback.java
similarity index 97%
rename from core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
rename to core/java/android/hardware/contexthub/HubEndpointLifecycleCallback.java
index 698ed0a..6d75010 100644
--- a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
+++ b/core/java/android/hardware/contexthub/HubEndpointLifecycleCallback.java
@@ -29,7 +29,7 @@
  */
 @SystemApi
 @FlaggedApi(Flags.FLAG_OFFLOAD_API)
-public interface IHubEndpointLifecycleCallback {
+public interface HubEndpointLifecycleCallback {
     /**
      * Called when an endpoint is requesting a session be opened with another endpoint.
      *
diff --git a/core/java/android/hardware/contexthub/IHubEndpointMessageCallback.java b/core/java/android/hardware/contexthub/HubEndpointMessageCallback.java
similarity index 89%
rename from core/java/android/hardware/contexthub/IHubEndpointMessageCallback.java
rename to core/java/android/hardware/contexthub/HubEndpointMessageCallback.java
index fde7017..5db54ef 100644
--- a/core/java/android/hardware/contexthub/IHubEndpointMessageCallback.java
+++ b/core/java/android/hardware/contexthub/HubEndpointMessageCallback.java
@@ -26,18 +26,18 @@
  * <p>This interface can be attached to an endpoint through {@link
  * HubEndpoint.Builder#setMessageCallback} method. Methods in this interface will only be called
  * when the endpoint is currently registered and has an open session. The endpoint will receive
- * session lifecycle callbacks through {@link IHubEndpointLifecycleCallback}.
+ * session lifecycle callbacks through {@link HubEndpointLifecycleCallback}.
  *
  * @hide
  */
 @SystemApi
 @FlaggedApi(Flags.FLAG_OFFLOAD_API)
-public interface IHubEndpointMessageCallback {
+public interface HubEndpointMessageCallback {
     /**
      * Callback interface for receiving messages for a particular endpoint session.
      *
      * @param session The session this message is sent through. Previously specified in a {@link
-     *     IHubEndpointLifecycleCallback#onSessionOpened(HubEndpointSession)} call.
+     *     HubEndpointLifecycleCallback#onSessionOpened(HubEndpointSession)} call.
      * @param message The {@link HubMessage} object representing a message received by the endpoint
      *     that registered this callback interface. This message is constructed by the
      */
diff --git a/core/java/android/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java
index 77f937e..f7f5636 100644
--- a/core/java/android/hardware/contexthub/HubEndpointSession.java
+++ b/core/java/android/hardware/contexthub/HubEndpointSession.java
@@ -137,7 +137,7 @@
      * no service associated to this session.
      *
      * <p>For hub initiated sessions, the object was previously used in as an argument for open
-     * request in {@link IHubEndpointLifecycleCallback#onSessionOpenRequest}.
+     * request in {@link HubEndpointLifecycleCallback#onSessionOpenRequest}.
      *
      * <p>For app initiated sessions, the object was previously used in an open request in {@link
      * android.hardware.location.ContextHubManager#openSession}
diff --git a/core/java/android/hardware/contexthub/HubEndpointSessionResult.java b/core/java/android/hardware/contexthub/HubEndpointSessionResult.java
index 1f2bdb9..b193d00 100644
--- a/core/java/android/hardware/contexthub/HubEndpointSessionResult.java
+++ b/core/java/android/hardware/contexthub/HubEndpointSessionResult.java
@@ -23,7 +23,7 @@
 import android.chre.flags.Flags;
 
 /**
- * Return type of {@link IHubEndpointLifecycleCallback#onSessionOpenRequest}. The value determines
+ * Return type of {@link HubEndpointLifecycleCallback#onSessionOpenRequest}. The value determines
  * whether a open session request from the remote is accepted or not.
  *
  * @hide
diff --git a/core/java/android/hardware/contexthub/HubServiceInfo.java b/core/java/android/hardware/contexthub/HubServiceInfo.java
index 2f33e8f..651be3b 100644
--- a/core/java/android/hardware/contexthub/HubServiceInfo.java
+++ b/core/java/android/hardware/contexthub/HubServiceInfo.java
@@ -112,6 +112,7 @@
      * <p>The value can be one of {@link HubServiceInfo#FORMAT_CUSTOM}, {@link
      * HubServiceInfo#FORMAT_AIDL} or {@link HubServiceInfo#FORMAT_PW_RPC_PROTOBUF}.
      */
+    @ServiceFormat
     public int getFormat() {
         return mFormat;
     }
@@ -178,7 +179,8 @@
          *   <li>Pigweed RPC with Protobuf: com.example.proto.ExampleService
          * </ol>
          *
-         * @param serviceDescriptor The service descriptor.
+         * @param serviceDescriptor The service descriptor for the interface, provided by the
+         *     vendor.
          * @param format One of {@link HubServiceInfo#FORMAT_CUSTOM}, {@link
          *     HubServiceInfo#FORMAT_AIDL} or {@link HubServiceInfo#FORMAT_PW_RPC_PROTOBUF}.
          * @param majorVersion Breaking changes should be a major version bump.
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index ffa5460..9030810 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -106,7 +106,7 @@
 
     @IntDef(prefix = {"EVENT_DISPLAY_"}, flag = true, value = {
             EVENT_DISPLAY_ADDED,
-            EVENT_DISPLAY_CHANGED,
+            EVENT_DISPLAY_BASIC_CHANGED,
             EVENT_DISPLAY_REMOVED,
             EVENT_DISPLAY_BRIGHTNESS_CHANGED,
             EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED,
@@ -119,7 +119,8 @@
     public @interface DisplayEvent {}
 
     public static final int EVENT_DISPLAY_ADDED = 1;
-    public static final int EVENT_DISPLAY_CHANGED = 2;
+    public static final int EVENT_DISPLAY_BASIC_CHANGED = 2;
+
     public static final int EVENT_DISPLAY_REMOVED = 3;
     public static final int EVENT_DISPLAY_BRIGHTNESS_CHANGED = 4;
     public static final int EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED = 5;
@@ -130,7 +131,7 @@
 
     @LongDef(prefix = {"INTERNAL_EVENT_FLAG_"}, flag = true, value = {
             INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
-            INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
+            INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED,
             INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
             INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED,
             INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED,
@@ -143,7 +144,7 @@
     public @interface InternalEventFlag {}
 
     public static final long INTERNAL_EVENT_FLAG_DISPLAY_ADDED = 1L << 0;
-    public static final long INTERNAL_EVENT_FLAG_DISPLAY_CHANGED = 1L << 1;
+    public static final long INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED = 1L << 1;
     public static final long INTERNAL_EVENT_FLAG_DISPLAY_REMOVED = 1L << 2;
     public static final long INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED = 1L << 3;
     public static final long INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED = 1L << 4;
@@ -485,7 +486,7 @@
         // There can be racing condition between DMS and WMS callbacks, so force triggering the
         // listener to make sure the client can get the onDisplayChanged callback even if
         // DisplayInfo is not changed (Display read from both DisplayInfo and WindowConfiguration).
-        handleDisplayEvent(displayId, EVENT_DISPLAY_CHANGED, true /* forceUpdate */);
+        handleDisplayEvent(displayId, EVENT_DISPLAY_BASIC_CHANGED, true /* forceUpdate */);
     }
 
     private static Looper getLooperForHandler(@Nullable Handler handler) {
@@ -518,7 +519,8 @@
         }
         if (mDispatchNativeCallbacks) {
             mask |= INTERNAL_EVENT_FLAG_DISPLAY_ADDED
-                    | INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+                    | INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+                    | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE
                     | INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
         }
         if (!mTopologyListeners.isEmpty()) {
@@ -571,7 +573,8 @@
             }
 
             info = getDisplayInfoLocked(displayId);
-            if (event == EVENT_DISPLAY_CHANGED && mDispatchNativeCallbacks) {
+            if ((event == EVENT_DISPLAY_BASIC_CHANGED
+                    || event == EVENT_DISPLAY_REFRESH_RATE_CHANGED) && mDispatchNativeCallbacks) {
                 // Choreographer only supports a single display, so only dispatch refresh rate
                 // changes for the default display.
                 if (displayId == Display.DEFAULT_DISPLAY) {
@@ -1492,9 +1495,9 @@
                         mListener.onDisplayAdded(displayId);
                     }
                     break;
-                case EVENT_DISPLAY_CHANGED:
-                    if ((mInternalEventFlagsMask & INTERNAL_EVENT_FLAG_DISPLAY_CHANGED)
-                            != 0) {
+                case EVENT_DISPLAY_BASIC_CHANGED:
+                    if ((mInternalEventFlagsMask
+                            & INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED) != 0) {
                         if (info != null && (forceUpdate || !info.equals(mDisplayInfo))) {
                             if (extraLogging()) {
                                 Slog.i(TAG, "Sending onDisplayChanged: Display Changed. Info: "
@@ -1691,8 +1694,8 @@
         switch (event) {
             case EVENT_DISPLAY_ADDED:
                 return "ADDED";
-            case EVENT_DISPLAY_CHANGED:
-                return "CHANGED";
+            case EVENT_DISPLAY_BASIC_CHANGED:
+                return "BASIC_CHANGED";
             case EVENT_DISPLAY_REMOVED:
                 return "REMOVED";
             case EVENT_DISPLAY_BRIGHTNESS_CHANGED:
@@ -1763,7 +1766,11 @@
         }
 
         if ((eventFlags & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) {
-            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_CHANGED;
+            // For backward compatibility, a client subscribing to
+            // DisplayManager.EVENT_FLAG_DISPLAY_CHANGED will be enrolled to both Basic and
+            // RR changes
+            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+                    | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
         }
 
         if ((eventFlags
diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java
index 211aeff..ba5dfc0 100644
--- a/core/java/android/hardware/display/DisplayTopology.java
+++ b/core/java/android/hardware/display/DisplayTopology.java
@@ -129,14 +129,38 @@
     }
 
     /**
+     * Update the size of a display and normalize the topology.
+     * @param displayId The logical display ID
+     * @param width The new width
+     * @param height The new height
+     * @return True if the topology has changed.
+     */
+    public boolean updateDisplay(int displayId, float width, float height) {
+        TreeNode display = findDisplay(displayId, mRoot);
+        if (display == null) {
+            return false;
+        }
+        if (floatEquals(display.mWidth, width) && floatEquals(display.mHeight, height)) {
+            return false;
+        }
+        display.mWidth = width;
+        display.mHeight = height;
+        normalize();
+        Slog.i(TAG, "Display with ID " + displayId + " updated, new width: " + width
+                + ", new height: " + height);
+        return true;
+    }
+
+    /**
      * Remove a display from the topology.
      * The default topology is created from the remaining displays, as if they were reconnected
      * one by one.
      * @param displayId The logical display ID
+     * @return True if the display was present in the topology and removed.
      */
-    public void removeDisplay(int displayId) {
+    public boolean removeDisplay(int displayId) {
         if (findDisplay(displayId, mRoot) == null) {
-            return;
+            return false;
         }
         Queue<TreeNode> queue = new ArrayDeque<>();
         queue.add(mRoot);
@@ -159,6 +183,7 @@
         } else {
             Slog.i(TAG, "Display with ID " + displayId + " removed");
         }
+        return true;
     }
 
     /**
@@ -685,12 +710,12 @@
         /**
          * The width of the display in density-independent pixels (dp).
          */
-        private final float mWidth;
+        private float mWidth;
 
         /**
          * The height of the display in density-independent pixels (dp).
          */
-        private final float mHeight;
+        private float mHeight;
 
         /**
          * The position of this display relative to its parent.
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 1e0cc94..0cd3209 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -36,11 +36,11 @@
 import android.hardware.contexthub.ErrorCode;
 import android.hardware.contexthub.HubDiscoveryInfo;
 import android.hardware.contexthub.HubEndpoint;
+import android.hardware.contexthub.HubEndpointDiscoveryCallback;
 import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.HubEndpointLifecycleCallback;
 import android.hardware.contexthub.HubServiceInfo;
 import android.hardware.contexthub.IContextHubEndpointDiscoveryCallback;
-import android.hardware.contexthub.IHubEndpointDiscoveryCallback;
-import android.hardware.contexthub.IHubEndpointLifecycleCallback;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.Looper;
@@ -207,7 +207,7 @@
     private Handler mCallbackHandler;
 
     /** A map of endpoint discovery callbacks currently registered */
-    private Map<IHubEndpointDiscoveryCallback, IContextHubEndpointDiscoveryCallback>
+    private Map<HubEndpointDiscoveryCallback, IContextHubEndpointDiscoveryCallback>
             mDiscoveryCallbacks = new ConcurrentHashMap<>();
 
     /**
@@ -718,7 +718,19 @@
     /**
      * Find a list of endpoints that provides a specific service.
      *
-     * @param serviceDescriptor Statically generated ID for an endpoint.
+     * <p>Service descriptor should uniquely identify the interface (scoped to type). Convention of
+     * the descriptor depend on interface type.
+     *
+     * <p>Examples:
+     *
+     * <ol>
+     *   <li>AOSP-defined AIDL: android.hardware.something.IFoo/default
+     *   <li>Vendor-defined AIDL: com.example.something.IBar/default
+     *   <li>Pigweed RPC with Protobuf: com.example.proto.ExampleService
+     * </ol>
+     *
+     * @param serviceDescriptor The service descriptor for a service provided by the hub. The value
+     *     cannot be null or empty.
      * @return A list of {@link HubDiscoveryInfo} objects that represents the result of discovery.
      * @throws IllegalArgumentException if the serviceDescriptor is empty/null.
      */
@@ -750,14 +762,15 @@
     /**
      * Creates an interface to invoke endpoint discovery callbacks to send down to the service.
      *
-     * @param callback the callback to invoke at the client process
      * @param executor the executor to invoke callbacks for this client
+     * @param callback the callback to invoke at the client process
+     * @param serviceDescriptor an optional descriptor to match discovery list with
      * @return the callback interface
      */
     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
     private IContextHubEndpointDiscoveryCallback createDiscoveryCallback(
-            IHubEndpointDiscoveryCallback callback,
             Executor executor,
+            HubEndpointDiscoveryCallback callback,
             @Nullable String serviceDescriptor) {
         return new IContextHubEndpointDiscoveryCallback.Stub() {
             @Override
@@ -829,36 +842,36 @@
     }
 
     /**
-     * Equivalent to {@link #registerEndpointDiscoveryCallback(long, IHubEndpointDiscoveryCallback,
-     * Executor)} with the default executor in the main thread.
+     * Equivalent to {@link #registerEndpointDiscoveryCallback(Executor,
+     * HubEndpointDiscoveryCallback, long)} with the default executor in the main thread.
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
     public void registerEndpointDiscoveryCallback(
-            long endpointId, @NonNull IHubEndpointDiscoveryCallback callback) {
+            @NonNull HubEndpointDiscoveryCallback callback, long endpointId) {
         registerEndpointDiscoveryCallback(
-                endpointId, callback, new HandlerExecutor(Handler.getMain()));
+                new HandlerExecutor(Handler.getMain()), callback, endpointId);
     }
 
     /**
      * Registers a callback to be notified when the hub endpoint with the corresponding endpoint ID
      * has started or stopped.
      *
-     * @param endpointId The identifier of the hub endpoint.
-     * @param callback The callback to be invoked.
      * @param executor The executor to invoke the callback on.
+     * @param callback The callback to be invoked.
+     * @param endpointId The identifier of the hub endpoint.
      * @throws UnsupportedOperationException If the operation is not supported.
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
     public void registerEndpointDiscoveryCallback(
-            long endpointId,
-            @NonNull IHubEndpointDiscoveryCallback callback,
-            @NonNull Executor executor) {
-        Objects.requireNonNull(callback, "callback cannot be null");
+            @NonNull Executor executor,
+            @NonNull HubEndpointDiscoveryCallback callback,
+            long endpointId) {
         Objects.requireNonNull(executor, "executor cannot be null");
+        Objects.requireNonNull(callback, "callback cannot be null");
         IContextHubEndpointDiscoveryCallback iCallback =
-                createDiscoveryCallback(callback, executor, null);
+                createDiscoveryCallback(executor, callback, null);
         try {
             mService.registerEndpointDiscoveryCallbackId(endpointId, iCallback);
         } catch (RemoteException e) {
@@ -869,42 +882,42 @@
     }
 
     /**
-     * Equivalent to {@link #registerEndpointDiscoveryCallback(String,
-     * IHubEndpointDiscoveryCallback, Executor)} with the default executor in the main thread.
+     * Equivalent to {@link #registerEndpointDiscoveryCallback(Executor,
+     * HubEndpointDiscoveryCallback, String)} with the default executor in the main thread.
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
     public void registerEndpointDiscoveryCallback(
-            @NonNull String serviceDescriptor, @NonNull IHubEndpointDiscoveryCallback callback) {
+            @NonNull HubEndpointDiscoveryCallback callback, @NonNull String serviceDescriptor) {
         registerEndpointDiscoveryCallback(
-                serviceDescriptor, callback, new HandlerExecutor(Handler.getMain()));
+                new HandlerExecutor(Handler.getMain()), callback, serviceDescriptor);
     }
 
     /**
      * Registers a callback to be notified when the hub endpoint with the corresponding service
      * descriptor has started or stopped.
      *
+     * @param executor The executor to invoke the callback on.
      * @param serviceDescriptor The service descriptor of the hub endpoint.
      * @param callback The callback to be invoked.
-     * @param executor The executor to invoke the callback on.
      * @throws IllegalArgumentException if the serviceDescriptor is empty.
      * @throws UnsupportedOperationException If the operation is not supported.
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
     public void registerEndpointDiscoveryCallback(
-            @NonNull String serviceDescriptor,
-            @NonNull IHubEndpointDiscoveryCallback callback,
-            @NonNull Executor executor) {
-        Objects.requireNonNull(serviceDescriptor, "serviceDescriptor cannot be null");
-        Objects.requireNonNull(callback, "callback cannot be null");
+            @NonNull Executor executor,
+            @NonNull HubEndpointDiscoveryCallback callback,
+            @NonNull String serviceDescriptor) {
         Objects.requireNonNull(executor, "executor cannot be null");
+        Objects.requireNonNull(callback, "callback cannot be null");
+        Objects.requireNonNull(serviceDescriptor, "serviceDescriptor cannot be null");
         if (serviceDescriptor.isBlank()) {
             throw new IllegalArgumentException("Invalid service descriptor: " + serviceDescriptor);
         }
 
         IContextHubEndpointDiscoveryCallback iCallback =
-                createDiscoveryCallback(callback, executor, serviceDescriptor);
+                createDiscoveryCallback(executor, callback, serviceDescriptor);
         try {
             mService.registerEndpointDiscoveryCallbackDescriptor(serviceDescriptor, iCallback);
         } catch (RemoteException e) {
@@ -924,7 +937,7 @@
     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
     public void unregisterEndpointDiscoveryCallback(
-            @NonNull IHubEndpointDiscoveryCallback callback) {
+            @NonNull HubEndpointDiscoveryCallback callback) {
         Objects.requireNonNull(callback, "callback cannot be null");
         IContextHubEndpointDiscoveryCallback iCallback = mDiscoveryCallbacks.remove(callback);
         if (iCallback == null) {
@@ -1291,7 +1304,7 @@
      * service.
      *
      * <p>Context Hub Service will create the endpoint session and notify the registered endpoint.
-     * The registered endpoint will receive callbacks on its {@link IHubEndpointLifecycleCallback}
+     * The registered endpoint will receive callbacks on its {@link HubEndpointLifecycleCallback}
      * object regarding the lifecycle events of the session.
      *
      * @param hubEndpoint {@link HubEndpoint} object previously registered via {@link
@@ -1311,7 +1324,7 @@
      * described by a {@link HubServiceInfo} object.
      *
      * <p>Context Hub Service will create the endpoint session and notify the registered endpoint.
-     * The registered endpoint will receive callbacks on its {@link IHubEndpointLifecycleCallback}
+     * The registered endpoint will receive callbacks on its {@link HubEndpointLifecycleCallback}
      * object regarding the lifecycle events of the session.
      *
      * @param hubEndpoint {@link HubEndpoint} object previously registered via {@link
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 8a0adfa..ecd90e4 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -480,10 +480,10 @@
             map.erase();
             map.ensureCapacity(count);
         }
-        int numLazyValues = 0;
+        int[] numLazyValues = new int[]{0};
         try {
-            numLazyValues = parcelledData.readArrayMap(map, count, !parcelledByNative,
-                    /* lazy */ ownsParcel, this);
+            parcelledData.readArrayMap(map, count, !parcelledByNative,
+                    /* lazy */ ownsParcel, this, numLazyValues);
         } catch (BadParcelableException e) {
             if (sShouldDefuse) {
                 Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
@@ -494,14 +494,14 @@
         } finally {
             mWeakParcelledData = null;
             if (ownsParcel) {
-                if (numLazyValues == 0) {
+                if (numLazyValues[0] == 0) {
                     recycleParcel(parcelledData);
                 } else {
                     mWeakParcelledData = new WeakReference<>(parcelledData);
                 }
             }
 
-            mLazyValues = numLazyValues;
+            mLazyValues = numLazyValues[0];
             mParcelledByNative = false;
             mMap = map;
             // Set field last as it is volatile
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 4bbc61c..5ba6553 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -5565,7 +5565,7 @@
 
     private void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal,
             int size, @Nullable ClassLoaderProvider loaderProvider) {
-        readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loaderProvider);
+        readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loaderProvider, null);
     }
 
     /**
@@ -5575,18 +5575,17 @@
      * @param lazy   Whether to populate the map with lazy {@link Function} objects for
      *               length-prefixed values. See {@link Parcel#readLazyValue(ClassLoader)} for more
      *               details.
-     * @return a count of the lazy values in the map
+     * @param lazyValueCount number of lazy values added here
      * @hide
      */
-    int readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted,
-            boolean lazy, @Nullable ClassLoaderProvider loaderProvider) {
-        int lazyValues = 0;
+    void readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted,
+            boolean lazy, @Nullable ClassLoaderProvider loaderProvider, int[] lazyValueCount) {
         while (size > 0) {
             String key = readString();
             Object value = (lazy) ? readLazyValue(loaderProvider) : readValue(
                     getClassLoader(loaderProvider));
             if (value instanceof LazyValue) {
-                lazyValues++;
+                lazyValueCount[0]++;
             }
             if (sorted) {
                 map.append(key, value);
@@ -5598,7 +5597,6 @@
         if (sorted) {
             map.validate();
         }
-        return lazyValues;
     }
 
     /**
diff --git a/core/java/android/os/TestLooperManager.java b/core/java/android/os/TestLooperManager.java
index e216992..289b98c 100644
--- a/core/java/android/os/TestLooperManager.java
+++ b/core/java/android/os/TestLooperManager.java
@@ -41,6 +41,7 @@
 
     private boolean mReleased;
     private boolean mLooperBlocked;
+    private final boolean mLooperIsMyLooper;
 
     /**
      * @hide
@@ -54,8 +55,11 @@
         }
         mLooper = looper;
         mQueue = mLooper.getQueue();
-        // Post a message that will keep the looper blocked as long as we are dispatching.
-        new Handler(looper).post(new LooperHolder());
+        mLooperIsMyLooper = Looper.myLooper() == looper;
+        if (!mLooperIsMyLooper) {
+            // Post a message that will keep the looper blocked as long as we are dispatching.
+            new Handler(looper).post(new LooperHolder());
+        }
     }
 
     /**
@@ -82,7 +86,7 @@
     public Message next() {
         // Wait for the looper block to come up, to make sure we don't accidentally get
         // the message for the block.
-        while (!mLooperBlocked) {
+        while (!mLooperIsMyLooper && !mLooperBlocked) {
             synchronized (this) {
                 try {
                     wait();
@@ -114,9 +118,6 @@
      * should be executed by this queue.
      * If the queue is empty or no messages are deliverable, returns null.
      * This method never blocks.
-     *
-     * <p>Callers should always call {@link #recycle(Message)} on the message when all interactions
-     * with it have completed.
      */
     @FlaggedApi(Flags.FLAG_MESSAGE_QUEUE_TESTABILITY)
     @SuppressWarnings("AutoBoxing")  // box the primitive long, or return null to indicate no value
@@ -165,6 +166,9 @@
             // This is being called from the thread it should be executed on, we can just dispatch.
             message.target.dispatchMessage(message);
         } else {
+            if (mLooperIsMyLooper) {
+                throw new RuntimeException("Cannot call execute from non Looper thread");
+            }
             MessageExecution execution = new MessageExecution();
             execution.m = message;
             synchronized (execution) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c3a4930..6898fce 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1296,6 +1296,22 @@
     public static final String ACTION_LOCKSCREEN_SETTINGS = "android.settings.LOCK_SCREEN_SETTINGS";
 
     /**
+     * Activity Action: Show settings of notifications on lockscreen.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_LOCKSCREEN_NOTIFICATIONS_SETTINGS =
+            "android.settings.LOCK_SCREEN_NOTIFICATIONS_SETTINGS";
+
+    /**
      * Activity Action: Show settings to allow pairing bluetooth devices.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
diff --git a/core/java/android/service/settings/preferences/GetValueRequest.java b/core/java/android/service/settings/preferences/GetValueRequest.java
index 4f82800..db5c57c 100644
--- a/core/java/android/service/settings/preferences/GetValueRequest.java
+++ b/core/java/android/service/settings/preferences/GetValueRequest.java
@@ -108,6 +108,7 @@
     /**
      * Builder to construct {@link GetValueRequest}.
      */
+    @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
     public static final class Builder {
         private final String mScreenKey;
         private final String mPreferenceKey;
diff --git a/core/java/android/service/settings/preferences/GetValueResult.java b/core/java/android/service/settings/preferences/GetValueResult.java
index 369dea7..7911315 100644
--- a/core/java/android/service/settings/preferences/GetValueResult.java
+++ b/core/java/android/service/settings/preferences/GetValueResult.java
@@ -170,6 +170,7 @@
     /**
      * Builder to construct {@link GetValueResult}.
      */
+    @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
     public static final class Builder {
         @ResultCode
         private final int mResultCode;
diff --git a/core/java/android/service/settings/preferences/MetadataRequest.java b/core/java/android/service/settings/preferences/MetadataRequest.java
index ffecc6b..e041715 100644
--- a/core/java/android/service/settings/preferences/MetadataRequest.java
+++ b/core/java/android/service/settings/preferences/MetadataRequest.java
@@ -65,6 +65,7 @@
     /**
      * Builder to construct {@link MetadataRequest}.
      */
+    @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
     public static final class Builder {
         /** Constructs an immutable {@link MetadataRequest} object. */
         @NonNull
diff --git a/core/java/android/service/settings/preferences/MetadataResult.java b/core/java/android/service/settings/preferences/MetadataResult.java
index 6a65dcc..e62fa8f 100644
--- a/core/java/android/service/settings/preferences/MetadataResult.java
+++ b/core/java/android/service/settings/preferences/MetadataResult.java
@@ -131,6 +131,7 @@
     /**
      * Builder to construct {@link MetadataResult}.
      */
+    @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
     public static final class Builder {
         @ResultCode
         private final int mResultCode;
diff --git a/core/java/android/service/settings/preferences/SetValueRequest.java b/core/java/android/service/settings/preferences/SetValueRequest.java
index f7600ae..77581d9 100644
--- a/core/java/android/service/settings/preferences/SetValueRequest.java
+++ b/core/java/android/service/settings/preferences/SetValueRequest.java
@@ -123,6 +123,7 @@
     /**
      * Builder to construct {@link SetValueRequest}.
      */
+    @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
     public static final class Builder {
         private final String mScreenKey;
         private final String mPreferenceKey;
diff --git a/core/java/android/service/settings/preferences/SetValueResult.java b/core/java/android/service/settings/preferences/SetValueResult.java
index cb1776a..513f7a7 100644
--- a/core/java/android/service/settings/preferences/SetValueResult.java
+++ b/core/java/android/service/settings/preferences/SetValueResult.java
@@ -156,6 +156,7 @@
     /**
      * Builder to construct {@link SetValueResult}.
      */
+    @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
     public static final class Builder {
         @ResultCode
         private final int mResultCode;
diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java b/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java
index ea7d4a6..30631f2 100644
--- a/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java
+++ b/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java
@@ -102,6 +102,7 @@
     /**
      * Returns the breadcrumbs (navigation context) of Preference.
      * <p>May be empty.
+     * @hide restrict to platform; may be opened wider in the future
      */
     @NonNull
     public List<String> getBreadcrumbs() {
@@ -189,33 +190,32 @@
     @IntDef(value = {
             NO_SENSITIVITY,
             EXPECT_POST_CONFIRMATION,
-            EXPECT_PRE_CONFIRMATION,
+            DEEPLINK_ONLY,
             NO_DIRECT_ACCESS,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface WriteSensitivity {}
 
     /**
-     * Indicates preference is not sensitive.
+     * Indicates preference is not write-sensitive.
      * <p>Its value is writable without explicit consent, assuming all necessary permissions are
      * granted.
      */
     public static final int NO_SENSITIVITY = 0;
     /**
-     * Indicates preference is mildly sensitive.
+     * Indicates preference is mildly write-sensitive.
      * <p>In addition to necessary permissions, after writing its value the user should be
      * given the option to revert back.
      */
     public static final int EXPECT_POST_CONFIRMATION = 1;
     /**
-     * Indicates preference is sensitive.
-     * <p>In addition to necessary permissions, the user should be prompted for confirmation prior
-     * to making a change. Otherwise it is suggested to provide a deeplink to the Preference's page
-     * instead, accessible via {@link #getLaunchIntent}.
+     * Indicates preference is write-sensitive.
+     * <p>This preference cannot be changed through this API; instead a deeplink to the Preference's
+     * page should be used instead, accessible via {@link #getLaunchIntent}.
      */
-    public static final int EXPECT_PRE_CONFIRMATION = 2;
+    public static final int DEEPLINK_ONLY = 2;
     /**
-     * Indicates preference is highly sensitivity and carries significant user-risk.
+     * Indicates preference is highly write-sensitivity and carries significant user-risk.
      * <p>This Preference cannot be changed through this API and no direct deeplink is available.
      * Other Metadata is still available.
      */
@@ -303,6 +303,7 @@
     /**
      * Builder to construct {@link SettingsPreferenceMetadata}.
      */
+    @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
     public static final class Builder {
         private final String mScreenKey;
         private final String mKey;
@@ -355,6 +356,7 @@
 
         /**
          * Sets the preference breadcrumbs (navigation context).
+         * @hide
          */
         @NonNull
         public Builder setBreadcrumbs(@NonNull List<String> breadcrumbs) {
diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceValue.java b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java
index 08826ca..eea93b3 100644
--- a/core/java/android/service/settings/preferences/SettingsPreferenceValue.java
+++ b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java
@@ -170,6 +170,7 @@
     /**
      * Builder to construct {@link SettingsPreferenceValue}.
      */
+    @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
     public static final class Builder {
         @Type
         private final int mType;
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 089b5c2..6c50b5f 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.view.flags.Flags.bufferStuffingRecovery;
 import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_API;
 import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP;
 import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER;
@@ -965,22 +966,24 @@
 
         // Evaluate if buffer stuffing recovery needs to start or end, and
         // what actions need to be taken for recovery.
-        switch (updateBufferStuffingState(frameTimeNanos, vsyncEventData)) {
-            case NONE:
-                // Without buffer stuffing recovery, offsetFrameTimeNanos is
-                // synonymous with frameTimeNanos.
-                break;
-            case OFFSET:
-                // Add animation offset. Used to update frame timeline with
-                // offset before jitter is calculated.
-                offsetFrameTimeNanos = frameTimeNanos - frameIntervalNanos;
-                break;
-            case DELAY_FRAME:
-                // Intentional frame delay to help reduce queued buffer count.
-                scheduleVsyncLocked();
-                return;
-            default:
-                break;
+        if (bufferStuffingRecovery()) {
+            switch (updateBufferStuffingState(frameTimeNanos, vsyncEventData)) {
+                case NONE:
+                    // Without buffer stuffing recovery, offsetFrameTimeNanos is
+                    // synonymous with frameTimeNanos.
+                    break;
+                case OFFSET:
+                    // Add animation offset. Used to update frame timeline with
+                    // offset before jitter is calculated.
+                    offsetFrameTimeNanos = frameTimeNanos - frameIntervalNanos;
+                    break;
+                case DELAY_FRAME:
+                    // Intentional frame delay to help reduce queued buffer count.
+                    scheduleVsyncLocked();
+                    return;
+                default:
+                    break;
+            }
         }
 
         try {
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 0c8a0d6..ca0959a 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1597,7 +1597,9 @@
             // Although we only care about the HDR/SDR ratio changing, that can also come in the
             // form of the larger DISPLAY_CHANGED event
             mGlobal.registerDisplayListener(toRegister, executor,
-                    DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+                    DisplayManagerGlobal
+                                    .INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+                            | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE
                             | DisplayManagerGlobal
                                     .INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED,
                     ActivityThread.currentPackageName());
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index ba098eb5..e75b1b0 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -447,7 +447,18 @@
     }
 
     public boolean equals(DisplayInfo other) {
-        return other != null
+        return equals(other, /* compareRefreshRate */ true);
+    }
+
+    /**
+     * Compares if the two DisplayInfo objects are equal or not
+     * @param other The other DisplayInfo against which the comparison is to be done
+     * @param compareRefreshRate Indicates if the refresh rate is also to be considered in
+     *                           comparison
+     * @return
+     */
+    public boolean equals(DisplayInfo other, boolean compareRefreshRate) {
+        boolean isEqualWithoutRefreshRate =  other != null
                 && layerStack == other.layerStack
                 && flags == other.flags
                 && type == other.type
@@ -466,7 +477,6 @@
                 && logicalHeight == other.logicalHeight
                 && Objects.equals(displayCutout, other.displayCutout)
                 && rotation == other.rotation
-                && modeId == other.modeId
                 && hasArrSupport == other.hasArrSupport
                 && Objects.equals(frameRateCategoryRate, other.frameRateCategoryRate)
                 && Arrays.equals(supportedRefreshRates, other.supportedRefreshRates)
@@ -490,7 +500,6 @@
                 && ownerUid == other.ownerUid
                 && Objects.equals(ownerPackageName, other.ownerPackageName)
                 && removeMode == other.removeMode
-                && getRefreshRate() == other.getRefreshRate()
                 && brightnessMinimum == other.brightnessMinimum
                 && brightnessMaximum == other.brightnessMaximum
                 && brightnessDefault == other.brightnessDefault
@@ -504,6 +513,13 @@
                 && Objects.equals(
                 thermalBrightnessThrottlingDataId, other.thermalBrightnessThrottlingDataId)
                 && canHostTasks == other.canHostTasks;
+
+        if (compareRefreshRate) {
+            return isEqualWithoutRefreshRate
+                    && (getRefreshRate() == other.getRefreshRate())
+                    && (modeId == other.modeId);
+        }
+        return isEqualWithoutRefreshRate;
     }
 
     @Override
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 6e6e87b..4fc1cfc 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -206,7 +206,8 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"FRAME_RATE_COMPATIBILITY_"},
             value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
-                    FRAME_RATE_COMPATIBILITY_GTE})
+                    FRAME_RATE_COMPATIBILITY_AT_LEAST, FRAME_RATE_COMPATIBILITY_EXACT,
+                    FRAME_RATE_COMPATIBILITY_MIN})
     public @interface FrameRateCompatibility {}
 
     // From native_window.h. Keep these in sync.
@@ -219,7 +220,7 @@
      * In Android version {@link Build.VERSION_CODES#BAKLAVA} and above, use
      * {@link FRAME_RATE_COMPATIBILITY_DEFAULT} for game content.
      * For other cases, see {@link FRAME_RATE_COMPATIBILITY_FIXED_SOURCE} and
-     * {@link FRAME_RATE_COMPATIBILITY_GTE}.
+     * {@link FRAME_RATE_COMPATIBILITY_AT_LEAST}.
      */
     public static final int FRAME_RATE_COMPATIBILITY_DEFAULT = 0;
 
@@ -234,7 +235,7 @@
     public static final int FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1;
 
     /**
-     * The surface requests a frame rate that is greater than or equal to the specified frame rate.
+     * The surface requests a frame rate that is at least the specified frame rate.
      * This value should be used for UIs, animations, scrolling and fling, and anything that is not
      * a game or video.
      *
@@ -242,7 +243,7 @@
      * {@link FRAME_RATE_COMPATIBILITY_DEFAULT}.
      */
     @FlaggedApi(com.android.graphics.surfaceflinger.flags.Flags.FLAG_ARR_SETFRAMERATE_GTE_ENUM)
-    public static final int FRAME_RATE_COMPATIBILITY_GTE = 2;
+    public static final int FRAME_RATE_COMPATIBILITY_AT_LEAST = 2;
 
     /**
      * This surface belongs to an app on the High Refresh Rate Deny list, and needs the display
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index f22505b..833f2d9 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -22,7 +22,6 @@
 import static android.graphics.Matrix.MSKEW_Y;
 import static android.graphics.Matrix.MTRANS_X;
 import static android.graphics.Matrix.MTRANS_Y;
-import static android.view.flags.Flags.bufferStuffingRecovery;
 import static android.view.SurfaceControlProto.HASH_CODE;
 import static android.view.SurfaceControlProto.LAYER_ID;
 import static android.view.SurfaceControlProto.NAME;
@@ -5118,11 +5117,9 @@
          */
         @NonNull
         public Transaction setRecoverableFromBufferStuffing(@NonNull SurfaceControl sc) {
-            if (bufferStuffingRecovery()) {
-                checkPreconditions(sc);
-                nativeSetFlags(mNativeObject, sc.mNativeObject, RECOVERABLE_FROM_BUFFER_STUFFING,
-                        RECOVERABLE_FROM_BUFFER_STUFFING);
-            }
+            checkPreconditions(sc);
+            nativeSetFlags(mNativeObject, sc.mNativeObject, RECOVERABLE_FROM_BUFFER_STUFFING,
+                    RECOVERABLE_FROM_BUFFER_STUFFING);
             return this;
         }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d13f0e2..8f8bfe2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -27,7 +27,7 @@
 import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
 import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
 import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
-import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_AT_LEAST;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
 import static android.view.accessibility.Flags.FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS;
@@ -34251,7 +34251,7 @@
                 compatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
                 frameRateToSet = frameRate;
             } else {
-                compatibility = FRAME_RATE_COMPATIBILITY_GTE;
+                compatibility = FRAME_RATE_COMPATIBILITY_AT_LEAST;
                 frameRateToSet = velocityFrameRate;
             }
             viewRootImpl.votePreferredFrameRate(frameRateToSet, compatibility);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1d27574..16cdb64 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -38,7 +38,7 @@
 import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
 import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
 import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
-import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_AT_LEAST;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_BOOST;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_CONFLICTED;
 import static android.view.View.FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
@@ -1828,7 +1828,8 @@
                         | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_STATE
                         | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED
                 : DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
-                        | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+                        | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+                        | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE
                         | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
         DisplayManagerGlobal
                 .getInstance()
@@ -13271,7 +13272,7 @@
      * We set category to HIGH if the maximum frame rate is greater than 60.
      * Otherwise, we set category to NORMAL.
      *
-     * Use FRAME_RATE_COMPATIBILITY_GTE for velocity and FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
+     * Use FRAME_RATE_COMPATIBILITY_AT_LEAST for velocity and FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
      * for TextureView video play and user requested frame rate.
      *
      * @param frameRate the preferred frame rate of a View
@@ -13282,7 +13283,7 @@
         if (frameRate <= 0) {
             return;
         }
-        if (frameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE && !mIsPressedGesture) {
+        if (frameRateCompatibility == FRAME_RATE_COMPATIBILITY_AT_LEAST && !mIsPressedGesture) {
             mIsTouchBoosting = false;
             mIsFrameRateBoosting = false;
             if (!sToolkitFrameRateVelocityMappingReadOnlyFlagValue) {
diff --git a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
index 20d193e..f709ed7 100644
--- a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
+++ b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
@@ -12,5 +12,5 @@
     name: "ccapi_baklava_enabled"
     namespace: "machine_learning"
     description: "Feature flag for baklava content capture API"
-    bug: "309411951"
+    bug: "380381249"
 }
diff --git a/core/java/android/window/WindowTokenClientController.java b/core/java/android/window/WindowTokenClientController.java
index 1101932..fcd7dfb 100644
--- a/core/java/android/window/WindowTokenClientController.java
+++ b/core/java/android/window/WindowTokenClientController.java
@@ -148,6 +148,9 @@
             info = wms.attachWindowContextToDisplayContent(mAppThread, client, displayId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
+        } catch (Exception e) {
+            Log.e(TAG, "Failed attachToDisplayContent", e);
+            return false;
         }
         if (info == null) {
             return false;
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 801698c..0d04961 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -58,13 +58,6 @@
 }
 
 flag {
-  name: "user_min_aspect_ratio_app_default"
-  namespace: "large_screen_experiences_app_compat"
-  description: "Whether the API PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT is available"
-  bug: "310816437"
-}
-
-flag {
   name: "allow_hide_scm_button"
   namespace: "large_screen_experiences_app_compat"
   description: "Whether we should allow hiding the size compat restart button"
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index ae84644..c97d3ec 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -148,3 +148,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    namespace: "windowing_sdk"
+    name: "condense_configuration_change_for_simple_mode"
+    description: "Condense configuration change for simple mode"
+    bug: "356738240"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/com/android/internal/jank/DisplayResolutionTracker.java b/core/java/com/android/internal/jank/DisplayResolutionTracker.java
index 0c2fd4b..5d66b3c 100644
--- a/core/java/com/android/internal/jank/DisplayResolutionTracker.java
+++ b/core/java/com/android/internal/jank/DisplayResolutionTracker.java
@@ -148,8 +148,8 @@
                 public void registerDisplayListener(DisplayManager.DisplayListener listener) {
                     manager.registerDisplayListener(listener, handler,
                             DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
-                                    | DisplayManagerGlobal
-                                            .INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
+                                    | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+                                    | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE,
                             ActivityThread.currentPackageName());
                 }
 
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 3e2f301..f14e1f6 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -236,4 +236,7 @@
 
     /** Shows rear display educational dialog */
     void showRearDisplayDialog(int currentBaseState);
+
+    /** Unbundle a categorized notification */
+    void unbundleNotification(String key);
 }
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 9bd5237..39ddea6 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -25,6 +25,8 @@
 import static android.security.Flags.reportPrimaryAuthAttempts;
 import static android.security.Flags.shouldTrustManagerListenForPrimaryAuth;
 
+import static com.android.internal.widget.flags.Flags.hideLastCharWithPhysicalInput;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -42,6 +44,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
+import android.hardware.input.InputManagerGlobal;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
@@ -59,6 +62,7 @@
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.SparseLongArray;
+import android.view.InputDevice;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
@@ -1097,12 +1101,20 @@
         return type == CREDENTIAL_TYPE_PATTERN;
     }
 
+    private boolean hasActivePointerDeviceAttached() {
+        return !getEnabledNonTouchInputDevices(InputDevice.SOURCE_CLASS_POINTER).isEmpty();
+    }
+
     /**
      * @return Whether the visible pattern is enabled.
      */
     @UnsupportedAppUsage
     public boolean isVisiblePatternEnabled(int userId) {
-        return getBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, true, userId);
+        boolean defaultValue = true;
+        if (hideLastCharWithPhysicalInput()) {
+            defaultValue = !hasActivePointerDeviceAttached();
+        }
+        return getBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, defaultValue, userId);
     }
 
     /**
@@ -1116,11 +1128,39 @@
         return getString(Settings.Secure.LOCK_PATTERN_VISIBLE, userId) != null;
     }
 
+    private List<InputDevice> getEnabledNonTouchInputDevices(int source) {
+        final InputManagerGlobal inputManager = InputManagerGlobal.getInstance();
+        final int[] inputIds = inputManager.getInputDeviceIds();
+        List<InputDevice> matchingDevices = new ArrayList<InputDevice>();
+        for (final int deviceId : inputIds) {
+            final InputDevice inputDevice = inputManager.getInputDevice(deviceId);
+            if (!inputDevice.isEnabled()) continue;
+            if (inputDevice.supportsSource(InputDevice.SOURCE_TOUCHSCREEN)) continue;
+            if (inputDevice.isVirtual()) continue;
+            if (!inputDevice.supportsSource(source)) continue;
+            matchingDevices.add(inputDevice);
+        }
+        return matchingDevices;
+    }
+
+    private boolean hasPhysicalKeyboardActive() {
+        final List<InputDevice> keyboards =
+                getEnabledNonTouchInputDevices(InputDevice.SOURCE_KEYBOARD);
+        for (final InputDevice keyboard : keyboards) {
+            if (keyboard.isFullKeyboard()) return true;
+        }
+        return false;
+    }
+
     /**
      * @return Whether enhanced pin privacy is enabled.
      */
     public boolean isPinEnhancedPrivacyEnabled(int userId) {
-        return getBoolean(LOCK_PIN_ENHANCED_PRIVACY, false, userId);
+        boolean defaultValue = false;
+        if (hideLastCharWithPhysicalInput()) {
+            defaultValue = hasPhysicalKeyboardActive();
+        }
+        return getBoolean(LOCK_PIN_ENHANCED_PRIVACY, defaultValue, userId);
     }
 
     /**
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 027113a..5acdf32 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -482,11 +482,22 @@
                 "libbinder",
                 "libhidlbase", // libhwbinder is in here
             ],
+            version_script: "platform/linux/libandroid_runtime_export.txt",
+        },
+        darwin: {
+            host_ldlibs: [
+                "-framework AppKit",
+            ],
+            dist: {
+                targets: ["layoutlib_jni"],
+                dir: "layoutlib_native/darwin",
+            },
+            exported_symbols_list: "platform/darwin/libandroid_runtime_export.exp",
         },
         linux_glibc_x86_64: {
             ldflags: ["-static-libgcc"],
             dist: {
-                targets: ["layoutlib"],
+                targets: ["layoutlib_jni"],
                 dir: "layoutlib_native/linux",
                 tag: "stripped_all",
             },
diff --git a/core/jni/android_hardware_camera2_CameraDevice.cpp b/core/jni/android_hardware_camera2_CameraDevice.cpp
index 493c707..04cfed5 100644
--- a/core/jni/android_hardware_camera2_CameraDevice.cpp
+++ b/core/jni/android_hardware_camera2_CameraDevice.cpp
@@ -30,6 +30,7 @@
 #include <nativehelper/JNIHelp.h>
 #include "android_os_Parcel.h"
 #include "core_jni_helpers.h"
+#include <android/binder_auto_utils.h>
 #include <android/binder_parcel_jni.h>
 #include <android/hardware/camera2/ICameraDeviceUser.h>
 #include <aidl/android/hardware/common/fmq/MQDescriptor.h>
@@ -40,6 +41,7 @@
 using namespace android;
 
 using ::android::AidlMessageQueue;
+using ndk::ScopedAParcel;
 using ResultMetadataQueue = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
 
 class FMQReader {
@@ -75,15 +77,16 @@
 
 static jlong CameraDevice_createFMQReader(JNIEnv *env, jclass thiz,
         jobject resultParcel) {
-    AParcel *resultAParcel = AParcel_fromJavaParcel(env, resultParcel);
-    if (resultAParcel == nullptr) {
+    ScopedAParcel sResultAParcel(AParcel_fromJavaParcel(env, resultParcel));
+    if (sResultAParcel.get() == nullptr) {
         ALOGE("%s: Error creating result parcel", __FUNCTION__);
         return 0;
     }
-    AParcel_setDataPosition(resultAParcel, 0);
+
+    AParcel_setDataPosition(sResultAParcel.get(), 0);
 
     MQDescriptor<int8_t, SynchronizedReadWrite> resultMQ;
-    if (resultMQ.readFromParcel(resultAParcel) != OK) {
+    if (resultMQ.readFromParcel(sResultAParcel.get()) != OK) {
         ALOGE("%s: read from result parcel failed", __FUNCTION__);
         return 0;
     }
diff --git a/core/jni/platform/darwin/libandroid_runtime_export.exp b/core/jni/platform/darwin/libandroid_runtime_export.exp
new file mode 100644
index 0000000..00a7585
--- /dev/null
+++ b/core/jni/platform/darwin/libandroid_runtime_export.exp
@@ -0,0 +1,38 @@
+#
+# 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.
+#
+
+# symbols needed for the JNI operations
+_JNI_OnLoad
+_ANativeWindow*
+
+# symbols needed to link with layoutlib_jni
+___android_log*
+__ZNK7android7RefBase*
+__ZN7android4base9SetLogger*
+__ZN7android4base10SetAborter*
+__ZN7android4base11GetProperty*
+__ZN7android4Rect*
+__ZN7android5Fence*
+__ZN7android7RefBase*
+__ZN7android7String*
+__ZN7android10VectorImpl*
+__ZN7android11BufferQueue*
+__ZN7android14AndroidRuntime*
+__ZN7android14sp_report_raceEv*
+__ZN7android15KeyCharacterMap*
+__ZN7android15InputDeviceInfo*
+__ZN7android31android_view_InputDevice_create*
+__ZN7android53android_view_Surface_createFromIGraphicBufferProducer*
diff --git a/core/jni/platform/linux/libandroid_runtime_export.txt b/core/jni/platform/linux/libandroid_runtime_export.txt
new file mode 100644
index 0000000..19e3478
--- /dev/null
+++ b/core/jni/platform/linux/libandroid_runtime_export.txt
@@ -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.
+#
+
+{
+  global:
+    # symbols needed for the JNI operations
+    JNI_OnLoad;
+    ANativeWindow*;
+
+    # symbols needed to link with layoutlib_jni
+    __android_log*;
+    _ZNK7android7RefBase*;
+    _ZN7android4base9SetLogger*;
+    _ZN7android4base10SetAborter*;
+    _ZN7android4base11GetProperty*;
+    _ZN7android4Rect*;
+    _ZN7android5Fence*;
+    _ZN7android7RefBase*;
+    _ZN7android7String*;
+    _ZN7android10VectorImpl*;
+    _ZN7android11BufferQueue*;
+    _ZN7android14AndroidRuntime*;
+    _ZN7android14sp_report_raceEv*;
+    _ZN7android15KeyCharacterMap*;
+    _ZN7android15InputDeviceInfo*;
+    _ZN7android31android_view_InputDevice_create*;
+    _ZN7android53android_view_Surface_createFromIGraphicBufferProducer*;
+  local:
+    *;
+};
diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index e28b646..e6295ea 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -100,4 +100,7 @@
     <!-- Whether to enable scaling and fading animation to scrollviews while scrolling.
          P.S this is a change only intended for wear devices. -->
     <bool name="config_enableViewGroupScalingFading">true</bool>
+
+    <!-- Allow the gesture to double tap the power button to trigger a target action. -->
+    <bool name="config_doubleTapPowerGestureEnabled">false</bool>
 </resources>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 9effeec..ca6ad6f 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -105,6 +105,7 @@
 
 import com.android.internal.R;
 import com.android.internal.util.ContrastColorUtil;
+import com.android.internal.widget.NotificationProgressModel;
 
 import junit.framework.Assert;
 
@@ -2414,7 +2415,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
-    public void progressStyle_getProgressMax_nooSegments_returnsDefault() {
+    public void progressStyle_getProgressMax_noSegments_returnsDefault() {
         final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
         progressStyle.setProgressSegments(Collections.emptyList());
         assertThat(progressStyle.getProgressMax()).isEqualTo(100);
@@ -2459,6 +2460,211 @@
 
     @Test
     @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+    public void progressStyle_getProgressMax_onSegmentLimitExceeded_returnsSumOfSegmentLength() {
+        // GIVEN
+        final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+        // limit is 10 for ProgressStyle
+        for (int i = 0; i < 30; i++) {
+            progressStyle
+                    .addProgressSegment(new Notification.ProgressStyle.Segment(10));
+        }
+
+        // THEN
+        assertThat(progressStyle.getProgressMax()).isEqualTo(300);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+    public void progressStyle_addProgressSegment_dropsInvalidSegments() {
+        // GIVEN
+        final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+        // Segments should be a positive integer.
+        progressStyle
+                .addProgressSegment(new Notification.ProgressStyle.Segment(0));
+        progressStyle
+                .addProgressSegment(new Notification.ProgressStyle.Segment(-1));
+
+        // THEN
+        assertThat(progressStyle.getProgressSegments()).isEmpty();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+    public void progressStyle_setProgressSegment_dropsInvalidSegments() {
+        // GIVEN
+        final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+        // Segments should be a positive integer.
+        progressStyle
+                .setProgressSegments(List.of(new Notification.ProgressStyle.Segment(0),
+                        new Notification.ProgressStyle.Segment(-1)));
+
+        // THEN
+        assertThat(progressStyle.getProgressSegments()).isEmpty();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+    public void progressStyle_addProgressPoint_dropsNegativePoints() {
+        // GIVEN
+        final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+        // Points should not be a negative integer.
+        progressStyle
+                .addProgressPoint(new Notification.ProgressStyle.Point(-1))
+                .addProgressPoint(new Notification.ProgressStyle.Point(-100));
+
+        // THEN
+        assertThat(progressStyle.getProgressPoints()).isEmpty();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+    public void progressStyle_setProgressPoint_dropsNegativePoints() {
+        // GIVEN
+        final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+        // Points should not be a negative integer.
+        progressStyle
+                .setProgressPoints(List.of(new Notification.ProgressStyle.Point(-1),
+                        new Notification.ProgressStyle.Point(-100)));
+
+        // THEN
+        assertThat(progressStyle.getProgressPoints()).isEmpty();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+    public void progressStyle_createProgressModel_ignoresPointsExceedingMax() {
+        // GIVEN
+        final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+        progressStyle.addProgressSegment(new Notification.ProgressStyle.Segment(100));
+        // Points should not larger than progress maximum.
+        progressStyle
+                .addProgressPoint(new Notification.ProgressStyle.Point(101))
+                .addProgressPoint(new Notification.ProgressStyle.Point(500));
+
+        // THEN
+        assertThat(progressStyle.createProgressModel(Color.BLUE, Color.RED).getPoints()).isEmpty();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+    public void progressStyle_createProgressModel_ignoresOverLimitPoints() {
+        // GIVEN
+        final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+        progressStyle.addProgressSegment(new Notification.ProgressStyle.Segment(100));
+
+        // maximum 4 points are going to be rendered.
+        progressStyle
+                .addProgressPoint(new Notification.ProgressStyle.Point(0))
+                .addProgressPoint(new Notification.ProgressStyle.Point(20))
+                .addProgressPoint(new Notification.ProgressStyle.Point(150))
+                .addProgressPoint(new Notification.ProgressStyle.Point(50))
+                .addProgressPoint(new Notification.ProgressStyle.Point(70))
+                .addProgressPoint(new Notification.ProgressStyle.Point(80))
+                .addProgressPoint(new Notification.ProgressStyle.Point(90))
+                .addProgressPoint(new Notification.ProgressStyle.Point(95))
+                .addProgressPoint(new Notification.ProgressStyle.Point(100));
+        final int backgroundColor = Color.RED;
+        final int defaultProgressColor = Color.BLUE;
+        final int expectedProgressColor = Notification.ProgressStyle.sanitizeProgressColor(
+                /* color = */Notification.COLOR_DEFAULT,
+                /* bg = */backgroundColor,
+                /* defaultColor = */defaultProgressColor);
+
+        // THEN
+        assertThat(progressStyle.createProgressModel(defaultProgressColor, backgroundColor)
+                .getPoints()).isEqualTo(
+                        List.of(new Notification.ProgressStyle.Point(0)
+                                .setColor(expectedProgressColor),
+                                new Notification.ProgressStyle.Point(20)
+                                .setColor(expectedProgressColor),
+                                new Notification.ProgressStyle.Point(50)
+                                .setColor(expectedProgressColor),
+                                new Notification.ProgressStyle.Point(70)
+                                .setColor(expectedProgressColor)
+                        )
+        );
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+    public void progressStyle_createProgressModel_mergeSegmentsOnOverflow() {
+        // GIVEN
+        final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+
+        for (int i = 0; i < 15; i++) {
+            progressStyle
+                    .addProgressSegment(new Notification.ProgressStyle.Segment(10));
+        }
+
+        final NotificationProgressModel progressModel = progressStyle.createProgressModel(
+                Color.BLUE, Color.RED);
+
+        // THEN
+        assertThat(progressModel.getSegments().size()).isEqualTo(1);
+        assertThat(progressModel.getProgressMax()).isEqualTo(150);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+    public void progressStyle_createProgressModel_useSegmentColorWhenAllMatch() {
+        // GIVEN
+        final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+        final int segmentColor = Color.YELLOW;
+        final int defaultProgressColor = Color.BLUE;
+        final int backgroundColor = Color.RED;
+        // contrast ensured color for segmentColor.
+        final int expectedSegmentColor = Notification.ProgressStyle.sanitizeProgressColor(
+                /* color = */   segmentColor,
+                /* bg = */  backgroundColor,
+                /* defaultColor = */ defaultProgressColor);
+
+        for (int i = 0; i < 15; i++) {
+            progressStyle
+                    .addProgressSegment(new Notification.ProgressStyle.Segment(10)
+                            .setColor(segmentColor));
+        }
+
+        final NotificationProgressModel progressModel = progressStyle.createProgressModel(
+                defaultProgressColor, backgroundColor);
+
+        // THEN
+        assertThat(progressModel.getSegments())
+                .isEqualTo(List.of(new Notification.ProgressStyle.Segment(150)
+                        .setColor(expectedSegmentColor)));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+    public void progressStyle_createProgressModel_useDefaultColorWhenAllNotMatch() {
+        // GIVEN
+        final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+        final int defaultProgressColor = Color.BLUE;
+        final int backgroundColor = Color.RED;
+        // contrast ensured color for default progress color.
+        final int expectedSegmentColor = Notification.ProgressStyle.sanitizeProgressColor(
+                /* color = */  defaultProgressColor,
+                /* bg = */ backgroundColor,
+                /* defaultColor = */ defaultProgressColor);
+
+        for (int i = 0; i < 15; i++) {
+            progressStyle
+                    .addProgressSegment(new Notification.ProgressStyle.Segment(5)
+                            .setColor(Color.BLUE))
+                    .addProgressSegment(new Notification.ProgressStyle.Segment(5)
+                            .setColor(Color.CYAN));
+        }
+
+        final NotificationProgressModel progressModel = progressStyle.createProgressModel(
+                defaultProgressColor, backgroundColor);
+
+        // THEN
+        assertThat(progressModel.getSegments())
+                .isEqualTo(List.of(new Notification.ProgressStyle.Segment(150)
+                        .setColor(expectedSegmentColor)));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
     public void progressStyle_indeterminate_defaultValueFalse() {
         final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
 
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 911b7ce..10a85bc 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -120,7 +120,8 @@
         doReturn(newDisplayInfo).when(mIDisplayManager).getDisplayInfo(123);
 
         mDisplayManager.registerDisplayListener(mListener, mHandler,
-                DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
+                DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+                        | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE,
                 null /* packageName */);
 
         mController.onDisplayChanged(123);
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 7a5b306..a270848 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -73,9 +73,13 @@
     public final CheckFlagsRule mCheckFlagsRule =
             DeviceFlagsValueProvider.createCheckFlagsRule();
 
+    private static final long DISPLAY_CHANGE_EVENTS =
+            DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+            | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+
     private static final long ALL_DISPLAY_EVENTS =
             DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
-            | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+            | DISPLAY_CHANGE_EVENTS
             | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
 
     @Mock
@@ -127,7 +131,7 @@
         final DisplayInfo newDisplayInfo = new DisplayInfo();
         newDisplayInfo.rotation++;
         doReturn(newDisplayInfo).when(mDisplayManager).getDisplayInfo(displayId);
-        callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+        callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
         waitForHandler();
         Mockito.verify(mDisplayListener).onDisplayChanged(eq(displayId));
         Mockito.verifyNoMoreInteractions(mDisplayListener);
@@ -186,8 +190,8 @@
 
         mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
                 ALL_DISPLAY_EVENTS
-                        & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, null);
-        callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+                        & ~DISPLAY_CHANGE_EVENTS, null);
+        callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
         waitForHandler();
         Mockito.verifyZeroInteractions(mDisplayListener);
 
@@ -257,8 +261,7 @@
                         | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
                 null /* packageName */);
         mDisplayManagerGlobal.registerDisplayListener(mDisplayListener2, mHandler,
-                DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
-                null /* packageName */);
+                DISPLAY_CHANGE_EVENTS, null /* packageName */);
 
         mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321);
         waitForHandler();
@@ -304,8 +307,7 @@
         assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
                 mDisplayManagerGlobal
                         .mapFlagsToInternalEventFlag(DisplayManager.EVENT_FLAG_DISPLAY_ADDED, 0));
-        assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
-                mDisplayManagerGlobal
+        assertEquals(DISPLAY_CHANGE_EVENTS, mDisplayManagerGlobal
                         .mapFlagsToInternalEventFlag(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, 0));
         assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
                 mDisplayManagerGlobal
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
index 4a227d8..255d09b 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
+++ b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
@@ -86,7 +86,7 @@
         verifyDisplay(display1, displayId1, width1, height1, noOfChildren = 1)
 
         val display2 = display1.children[0]
-        verifyDisplay(display1.children[0], displayId2, width2, height2, POSITION_TOP,
+        verifyDisplay(display2, displayId2, width2, height2, POSITION_TOP,
             offset = width1 / 2 - width2 / 2, noOfChildren = 1)
 
         var display = display2
@@ -99,6 +99,76 @@
     }
 
     @Test
+    fun updateDisplay() {
+        val displayId = 1
+        val width = 800f
+        val height = 600f
+
+        val newWidth = 1000f
+        val newHeight = 500f
+        topology.addDisplay(displayId, width, height)
+        assertThat(topology.updateDisplay(displayId, newWidth, newHeight)).isTrue()
+
+        assertThat(topology.primaryDisplayId).isEqualTo(displayId)
+        verifyDisplay(topology.root!!, displayId, newWidth, newHeight, noOfChildren = 0)
+    }
+
+    @Test
+    fun updateDisplay_notUpdated() {
+        val displayId = 1
+        val width = 800f
+        val height = 600f
+        topology.addDisplay(displayId, width, height)
+
+        // Same size
+        assertThat(topology.updateDisplay(displayId, width, height)).isFalse()
+
+        // Display doesn't exist
+        assertThat(topology.updateDisplay(/* displayId= */ 100, width, height)).isFalse()
+
+        assertThat(topology.primaryDisplayId).isEqualTo(displayId)
+        verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0)
+    }
+
+    @Test
+    fun updateDisplayDoesNotAffectDefaultTopology() {
+        val width1 = 700f
+        val height = 600f
+        topology.addDisplay(/* displayId= */ 1, width1, height)
+
+        val width2 = 800f
+        val noOfDisplays = 30
+        for (i in 2..noOfDisplays) {
+            topology.addDisplay(/* displayId= */ i, width2, height)
+        }
+
+        val displaysToUpdate = arrayOf(3, 7, 18)
+        val newWidth = 1000f
+        val newHeight = 1500f
+        for (i in displaysToUpdate) {
+            assertThat(topology.updateDisplay(/* displayId= */ i, newWidth, newHeight)).isTrue()
+        }
+
+        assertThat(topology.primaryDisplayId).isEqualTo(1)
+
+        val display1 = topology.root!!
+        verifyDisplay(display1, id = 1, width1, height, noOfChildren = 1)
+
+        val display2 = display1.children[0]
+        verifyDisplay(display2, id = 2, width2, height, POSITION_TOP,
+            offset = width1 / 2 - width2 / 2, noOfChildren = 1)
+
+        var display = display2
+        for (i in 3..noOfDisplays) {
+            display = display.children[0]
+            // The last display should have no children
+            verifyDisplay(display, id = i, if (i in displaysToUpdate) newWidth else width2,
+                if (i in displaysToUpdate) newHeight else height, POSITION_RIGHT, offset = 0f,
+                noOfChildren = if (i < noOfDisplays) 1 else 0)
+        }
+    }
+
+    @Test
     fun removeDisplays() {
         val displayId1 = 1
         val width1 = 800f
@@ -117,7 +187,7 @@
         }
 
         var removedDisplays = arrayOf(20)
-        topology.removeDisplay(20)
+        assertThat(topology.removeDisplay(20)).isTrue()
 
         assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
 
@@ -139,11 +209,11 @@
                 noOfChildren = if (i < noOfDisplays) 1 else 0)
         }
 
-        topology.removeDisplay(22)
+        assertThat(topology.removeDisplay(22)).isTrue()
         removedDisplays += 22
-        topology.removeDisplay(23)
+        assertThat(topology.removeDisplay(23)).isTrue()
         removedDisplays += 23
-        topology.removeDisplay(25)
+        assertThat(topology.removeDisplay(25)).isTrue()
         removedDisplays += 25
 
         assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
@@ -174,7 +244,7 @@
         val height = 600f
 
         topology.addDisplay(displayId, width, height)
-        topology.removeDisplay(displayId)
+        assertThat(topology.removeDisplay(displayId)).isTrue()
 
         assertThat(topology.primaryDisplayId).isEqualTo(Display.INVALID_DISPLAY)
         assertThat(topology.root).isNull()
@@ -187,7 +257,7 @@
         val height = 600f
 
         topology.addDisplay(displayId, width, height)
-        topology.removeDisplay(3)
+        assertThat(topology.removeDisplay(3)).isFalse()
 
         assertThat(topology.primaryDisplayId).isEqualTo(displayId)
         verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0)
@@ -203,7 +273,7 @@
         topology = DisplayTopology(/* root= */ null, displayId2)
         topology.addDisplay(displayId1, width, height)
         topology.addDisplay(displayId2, width, height)
-        topology.removeDisplay(displayId2)
+        assertThat(topology.removeDisplay(displayId2)).isTrue()
 
         assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
         verifyDisplay(topology.root!!, displayId1, width, height, noOfChildren = 0)
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index ed9fc1c..18ab52d 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -25,7 +25,7 @@
 import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
 import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
 import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
-import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_AT_LEAST;
 import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
@@ -861,10 +861,10 @@
             assertEquals(mViewRootImpl.getFrameRateCompatibility(),
                     FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
             assertFalse(mViewRootImpl.isFrameRateConflicted());
-            mViewRootImpl.votePreferredFrameRate(24, FRAME_RATE_COMPATIBILITY_GTE);
+            mViewRootImpl.votePreferredFrameRate(24, FRAME_RATE_COMPATIBILITY_AT_LEAST);
             if (toolkitFrameRateVelocityMappingReadOnly()) {
                 assertEquals(24, mViewRootImpl.getPreferredFrameRate(), 0.1);
-                assertEquals(FRAME_RATE_COMPATIBILITY_GTE,
+                assertEquals(FRAME_RATE_COMPATIBILITY_AT_LEAST,
                         mViewRootImpl.getFrameRateCompatibility());
                 assertFalse(mViewRootImpl.isFrameRateConflicted());
             } else {
@@ -888,10 +888,10 @@
 
         sInstrumentation.runOnMainSync(() -> {
             assertFalse(mViewRootImpl.isFrameRateConflicted());
-            mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_GTE);
+            mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_AT_LEAST);
             if (toolkitFrameRateVelocityMappingReadOnly()) {
                 assertEquals(60, mViewRootImpl.getPreferredFrameRate(), 0.1);
-                assertEquals(FRAME_RATE_COMPATIBILITY_GTE,
+                assertEquals(FRAME_RATE_COMPATIBILITY_AT_LEAST,
                         mViewRootImpl.getFrameRateCompatibility());
             } else {
                 assertEquals(FRAME_RATE_CATEGORY_HIGH,
@@ -904,7 +904,7 @@
                     mViewRootImpl.getFrameRateCompatibility());
             // Should be false since 60 is a divisor of 120.
             assertFalse(mViewRootImpl.isFrameRateConflicted());
-            mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_GTE);
+            mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_AT_LEAST);
             assertEquals(120, mViewRootImpl.getPreferredFrameRate(), 0.1);
             // compatibility should be remained the same (FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
             // since the frame rate 60 is smaller than 120.
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
index 00b4f46..d1fbc77c 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
@@ -31,6 +31,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doNothing;
@@ -44,20 +45,27 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.pm.UserInfo;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManagerGlobal;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.platform.test.ravenwood.RavenwoodRule;
 import android.provider.Settings;
 import android.test.mock.MockContentResolver;
+import android.view.InputDevice;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.widget.flags.Flags;
 
 import com.google.android.collect.Lists;
 
@@ -76,6 +84,8 @@
 public class LockPatternUtilsTest {
     @Rule
     public final RavenwoodRule mRavenwood = new RavenwoodRule();
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     private ILockSettings mLockSettings;
     private static final int USER_ID = 1;
@@ -395,4 +405,156 @@
             }
         };
     }
+
+    private InputManagerGlobal.TestSession configureExternalHardwareTest(InputDevice[] devices)
+            throws RemoteException {
+        final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext());
+        final ILockSettings ils = mock(ILockSettings.class);
+        when(ils.getBoolean(anyString(), anyBoolean(), anyInt())).thenThrow(RemoteException.class);
+        mLockPatternUtils = new LockPatternUtils(context, ils);
+
+        IInputManager inputManagerMock = mock(IInputManager.class);
+
+        int[] deviceIds = new int[devices.length];
+
+        for (int i = 0; i < devices.length; i++) {
+            when(inputManagerMock.getInputDevice(i)).thenReturn(devices[i]);
+        }
+
+        when(inputManagerMock.getInputDeviceIds()).thenReturn(deviceIds);
+        InputManagerGlobal.TestSession session =
+                InputManagerGlobal.createTestSession(inputManagerMock);
+
+        return session;
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void isPinEnhancedPrivacyEnabled_noDevicesAttached() throws RemoteException {
+        InputManagerGlobal.TestSession session = configureExternalHardwareTest(new InputDevice[0]);
+        assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
+        session.close();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void isPinEnhancedPrivacyEnabled_noEnabledDeviceAttached() throws RemoteException {
+        InputDevice.Builder builder = new InputDevice.Builder();
+        builder.setEnabled(false);
+        InputManagerGlobal.TestSession session =
+                configureExternalHardwareTest(new InputDevice[]{builder.build()});
+        assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
+        session.close();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void isPinEnhancedPrivacyEnabled_withoutHwKeyboard() throws RemoteException {
+        InputDevice.Builder builder = new InputDevice.Builder();
+        builder.setEnabled(true).setSources(InputDevice.SOURCE_TOUCHSCREEN);
+        InputManagerGlobal.TestSession session =
+                configureExternalHardwareTest(new InputDevice[]{builder.build()});
+        assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
+        session.close();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void isPinEnhancedPrivacyEnabled_withoutFullHwKeyboard() throws RemoteException {
+        InputDevice.Builder builder = new InputDevice.Builder();
+        builder
+                .setEnabled(true)
+                .setSources(InputDevice.SOURCE_KEYBOARD)
+                .setKeyboardType(InputDevice.KEYBOARD_TYPE_NON_ALPHABETIC);
+        InputManagerGlobal.TestSession session =
+                configureExternalHardwareTest(new InputDevice[]{builder.build()});
+        assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
+        session.close();
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void isPinEnhancedPrivacyEnabled_withHwKeyboardOldDefault() throws RemoteException {
+        InputDevice.Builder builder = new InputDevice.Builder();
+        builder
+                .setEnabled(true)
+                .setSources(InputDevice.SOURCE_KEYBOARD)
+                .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC);
+        InputManagerGlobal.TestSession session =
+                configureExternalHardwareTest(new InputDevice[]{builder.build()});
+        assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
+        session.close();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void isPinEnhancedPrivacyEnabled_withHwKeyboard() throws RemoteException {
+        InputDevice.Builder builder = new InputDevice.Builder();
+        builder
+                .setEnabled(true)
+                .setSources(InputDevice.SOURCE_KEYBOARD)
+                .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC);
+        InputManagerGlobal.TestSession session =
+                configureExternalHardwareTest(new InputDevice[]{builder.build()});
+        assertTrue(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
+        session.close();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void isVisiblePatternEnabled_noDevices() throws RemoteException {
+        InputManagerGlobal.TestSession session = configureExternalHardwareTest(new InputDevice[0]);
+        assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID));
+        session.close();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void isVisiblePatternEnabled_noEnabledDevices() throws RemoteException {
+        InputDevice.Builder builder = new InputDevice.Builder();
+        builder.setEnabled(false);
+        InputManagerGlobal.TestSession session =
+                configureExternalHardwareTest(new InputDevice[]{builder.build()});
+        assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID));
+        session.close();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void isVisiblePatternEnabled_noPointingDevices() throws RemoteException {
+        InputDevice.Builder builder = new InputDevice.Builder();
+        builder
+                .setEnabled(true)
+                .setSources(InputDevice.SOURCE_TOUCHSCREEN);
+        InputManagerGlobal.TestSession session =
+                configureExternalHardwareTest(new InputDevice[]{builder.build()});
+        assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID));
+        session.close();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void isVisiblePatternEnabled_externalPointingDevice() throws RemoteException {
+        InputDevice.Builder builder = new InputDevice.Builder();
+        builder
+                .setEnabled(true)
+                .setSources(InputDevice.SOURCE_CLASS_POINTER);
+        InputManagerGlobal.TestSession session =
+                configureExternalHardwareTest(new InputDevice[]{builder.build()});
+        assertFalse(mLockPatternUtils.isVisiblePatternEnabled(USER_ID));
+        session.close();
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+    public void isVisiblePatternEnabled_externalPointingDeviceOldDefault() throws RemoteException {
+        InputDevice.Builder builder = new InputDevice.Builder();
+        builder
+                .setEnabled(true)
+                .setSources(InputDevice.SOURCE_CLASS_POINTER);
+        InputManagerGlobal.TestSession session =
+                configureExternalHardwareTest(new InputDevice[]{builder.build()});
+        assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID));
+        session.close();
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 21c44c9..4bcec70 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -571,9 +571,9 @@
             // For flexible split, expand app offscreen as well
             if (mDividerSnapAlgorithm.areOffscreenRatiosSupported()) {
                 if (position <= mDividerSnapAlgorithm.getMiddleTarget().position) {
-                    bounds1.top = bounds1.bottom - bounds2.width();
+                    bounds1.top = bounds1.bottom - bounds2.height();
                 } else {
-                    bounds2.bottom = bounds2.top + bounds1.width();
+                    bounds2.bottom = bounds2.top + bounds1.height();
                 }
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index dfa2d9b..9a60cfe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -38,6 +38,7 @@
 import com.android.internal.protolog.ProtoLog
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON
@@ -236,6 +237,7 @@
         ) {
             // Sessions is finishing, log task updates followed by an exit event
             identifyAndLogTaskUpdates(
+                transitionInfo,
                 preTransitionVisibleFreeformTasks,
                 postTransitionVisibleFreeformTasks,
             )
@@ -252,12 +254,14 @@
             desktopModeEventLogger.logSessionEnter(getEnterReason(transitionInfo))
 
             identifyAndLogTaskUpdates(
+                transitionInfo,
                 preTransitionVisibleFreeformTasks,
                 postTransitionVisibleFreeformTasks,
             )
         } else if (isSessionActive) {
             // Session is neither starting, nor finishing, log task updates if there are any
             identifyAndLogTaskUpdates(
+                transitionInfo,
                 preTransitionVisibleFreeformTasks,
                 postTransitionVisibleFreeformTasks,
             )
@@ -270,6 +274,7 @@
 
     /** Compare the old and new state of taskInfos and identify and log the changes */
     private fun identifyAndLogTaskUpdates(
+        transitionInfo: TransitionInfo,
         preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
         postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
     ) {
@@ -304,9 +309,19 @@
         // find old tasks that were removed
         preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
             if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) {
-                desktopModeEventLogger.logTaskRemoved(
-                    buildTaskUpdateForTask(taskInfo, postTransitionVisibleFreeformTasks.size())
-                )
+                val minimizeReason =
+                    if (transitionInfo.type == Transitions.TRANSIT_MINIMIZE) {
+                        MinimizeReason.MINIMIZE_BUTTON
+                    } else {
+                        null
+                    }
+                val taskUpdate =
+                    buildTaskUpdateForTask(
+                        taskInfo,
+                        postTransitionVisibleFreeformTasks.size(),
+                        minimizeReason,
+                    )
+                desktopModeEventLogger.logTaskRemoved(taskUpdate)
                 Trace.setCounter(
                     Trace.TRACE_TAG_WINDOW_MANAGER,
                     VISIBLE_TASKS_COUNTER_NAME,
@@ -320,7 +335,11 @@
         }
     }
 
-    private fun buildTaskUpdateForTask(taskInfo: TaskInfo, visibleTasks: Int): TaskUpdate {
+    private fun buildTaskUpdateForTask(
+        taskInfo: TaskInfo,
+        visibleTasks: Int,
+        minimizeReason: MinimizeReason? = null,
+    ): TaskUpdate {
         val screenBounds = taskInfo.configuration.windowConfiguration.bounds
         val positionInParent = taskInfo.positionInParent
         return TaskUpdate(
@@ -331,6 +350,7 @@
             taskX = positionInParent.x,
             taskY = positionInParent.y,
             visibleTaskCount = visibleTasks,
+            minimizeReason = minimizeReason,
         )
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index 606a729..9019134 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -82,7 +82,7 @@
                         // For portrait resizeable activities, respect apps fullscreen width but
                         // apply ideal size height.
                         Size(
-                            taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth,
+                            taskInfo.appCompatTaskInfo.topActivityAppBounds.width(),
                             idealSize.height,
                         )
                     } else {
@@ -104,7 +104,7 @@
                         // apply custom app width.
                         Size(
                             customPortraitWidthForLandscapeApp,
-                            taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight,
+                            taskInfo.appCompatTaskInfo.topActivityAppBounds.height(),
                         )
                     } else {
                         // For portrait resizeable activities, simply apply ideal size.
@@ -196,13 +196,8 @@
 
 /** Calculates the aspect ratio of an activity from its fullscreen bounds. */
 fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
-    val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth
-    val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight
-    if (taskInfo.appCompatTaskInfo.isTopActivityLetterboxed || !taskInfo.canChangeAspectRatio) {
-        return maxOf(appLetterboxWidth, appLetterboxHeight) /
-            minOf(appLetterboxWidth, appLetterboxHeight).toFloat()
-    }
-    val appBounds = taskInfo.configuration.windowConfiguration.appBounds ?: return 1f
+    if (taskInfo.appCompatTaskInfo.topActivityAppBounds.isEmpty) return 1f
+    val appBounds = taskInfo.appCompatTaskInfo.topActivityAppBounds
     return maxOf(appBounds.height(), appBounds.width()) /
         minOf(appBounds.height(), appBounds.width()).toFloat()
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
index a611fe1..c4ff87d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
@@ -74,7 +74,7 @@
         mInitialDragData = data;
         mInitialDragFlags = dragFlags;
         displayLayout = dispLayout;
-        hideDragSourceTaskId = data.getDescription().getExtras() != null
+        hideDragSourceTaskId = data != null && data.getDescription().getExtras() != null
                 ? data.getDescription().getExtras().getInt(EXTRA_HIDE_DRAG_SOURCE_TASK_ID, -1)
                 : -1;
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsWithKeyboard.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsWithKeyboard.kt
new file mode 100644
index 0000000..56f1dcb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsWithKeyboard.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.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_APP
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_LAST_APP
+import com.android.wm.shell.scenarios.MinimizeAppWindows
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Minimize app windows by pressing META + -.
+ *
+ * Assert that the app windows gets hidden.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MinimizeAppsWithKeyboard : MinimizeAppWindows(usingKeyboard = true) {
+    @ExpectedScenarios(["MINIMIZE_APP", "MINIMIZE_LAST_APP"])
+    @Test
+    override fun minimizeAllAppWindows() = super.minimizeAllAppWindows()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig()
+                .use(FlickerServiceConfig.DEFAULT)
+                .use(MINIMIZE_APP)
+                .use(MINIMIZE_LAST_APP)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
index 971637b..835559c 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
@@ -43,7 +43,10 @@
  */
 @Ignore("Test Base Class")
 abstract class MinimizeAppWindows
-constructor(private val rotation: Rotation = Rotation.ROTATION_0) {
+constructor(
+    private val rotation: Rotation = Rotation.ROTATION_0,
+    private val usingKeyboard: Boolean = false
+) {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val tapl = LauncherInstrumentation()
     private val wmHelper = WindowManagerStateHelper(instrumentation)
@@ -68,9 +71,9 @@
 
     @Test
     open fun minimizeAllAppWindows() {
-        testApp3.minimizeDesktopApp(wmHelper, device)
-        testApp2.minimizeDesktopApp(wmHelper, device)
-        testApp1.minimizeDesktopApp(wmHelper, device)
+        testApp3.minimizeDesktopApp(wmHelper, device, usingKeyboard = usingKeyboard)
+        testApp2.minimizeDesktopApp(wmHelper, device, usingKeyboard = usingKeyboard)
+        testApp1.minimizeDesktopApp(wmHelper, device, usingKeyboard = usingKeyboard)
     }
 
     @After
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index abd7078..c0ff2f0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -45,6 +45,7 @@
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
 import com.google.common.truth.Truth.assertThat
+import org.junit.After
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -77,6 +78,12 @@
         doReturn(DISPLAY_HEIGHT).whenever(displayLayout).height()
     }
 
+    @After
+    fun tearDown() {
+        clearInvocations(staticMockMarker(FrameworkStatsLog::class.java))
+        clearInvocations(staticMockMarker(EventLogTags::class.java))
+    }
+
     @Test
     fun logSessionEnter_logsEnterReasonWithNewSessionId() {
         desktopModeEventLogger.logSessionEnter(EnterReason.KEYBOARD_SHORTCUT_ENTER)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index 43684fb..0154ff3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -46,6 +46,7 @@
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON
@@ -566,7 +567,10 @@
 
         assertFalse(transitionObserver.isSessionActive)
         verify(desktopModeEventLogger, times(1)).logSessionExit(eq(ExitReason.TASK_MINIMIZED))
-        verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(DEFAULT_TASK_UPDATE))
+        verify(desktopModeEventLogger, times(1))
+            .logTaskRemoved(
+                eq(DEFAULT_TASK_UPDATE.copy(minimizeReason = MinimizeReason.MINIMIZE_BUTTON))
+            )
         verifyZeroInteractions(desktopModeEventLogger)
     }
 
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 4f37180b..e1c2153 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
@@ -4160,8 +4160,7 @@
                         screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
                         configuration.windowConfiguration.appBounds = bounds
                     }
-                appCompatTaskInfo.topActivityLetterboxAppWidth = bounds.width()
-                appCompatTaskInfo.topActivityLetterboxAppHeight = bounds.height()
+                appCompatTaskInfo.topActivityAppBounds.set(0, 0, bounds.width(), bounds.height())
                 isResizeable = false
             }
 
@@ -4879,15 +4878,19 @@
             appCompatTaskInfo.isSystemFullscreenOverrideEnabled = enableSystemFullscreenOverride
 
             if (deviceOrientation == ORIENTATION_LANDSCAPE) {
-                configuration.windowConfiguration.appBounds =
-                    Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT)
-                appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_LONG
-                appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_SHORT
+                appCompatTaskInfo.topActivityAppBounds.set(
+                    0,
+                    0,
+                    DISPLAY_DIMENSION_LONG,
+                    DISPLAY_DIMENSION_SHORT,
+                )
             } else {
-                configuration.windowConfiguration.appBounds =
-                    Rect(0, 0, DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG)
-                appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_SHORT
-                appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_LONG
+                appCompatTaskInfo.topActivityAppBounds.set(
+                    0,
+                    0,
+                    DISPLAY_DIMENSION_SHORT,
+                    DISPLAY_DIMENSION_LONG,
+                )
             }
 
             if (shouldLetterbox) {
@@ -4897,17 +4900,15 @@
                         screenOrientation == SCREEN_ORIENTATION_PORTRAIT
                 ) {
                     // Letterbox to portrait size
-                    appCompatTaskInfo.setTopActivityLetterboxed(true)
-                    appCompatTaskInfo.topActivityLetterboxAppWidth = 1200
-                    appCompatTaskInfo.topActivityLetterboxAppHeight = 1600
+                    appCompatTaskInfo.isTopActivityLetterboxed = true
+                    appCompatTaskInfo.topActivityAppBounds.set(0, 0, 1200, 1600)
                 } else if (
                     deviceOrientation == ORIENTATION_PORTRAIT &&
                         screenOrientation == SCREEN_ORIENTATION_LANDSCAPE
                 ) {
                     // Letterbox to landscape size
-                    appCompatTaskInfo.setTopActivityLetterboxed(true)
-                    appCompatTaskInfo.topActivityLetterboxAppWidth = 1600
-                    appCompatTaskInfo.topActivityLetterboxAppHeight = 1200
+                    appCompatTaskInfo.isTopActivityLetterboxed = true
+                    appCompatTaskInfo.topActivityAppBounds.set(0, 0, 1600, 1200)
                 }
             }
         }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt
index 3d59342..8ccca07 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt
@@ -59,6 +59,13 @@
     }
 
     @Test
+    fun testNullClipData() {
+        // Start a new drag session with null data
+        val session = DragSession(activityTaskManager, displayLayout, null, 0)
+        assertThat(session.hideDragSourceTaskId).isEqualTo(-1)
+    }
+
+    @Test
     fun testGetRunningTask() {
         // Set up running tasks
         val runningTasks = listOf(
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index e618245..5955915 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -1420,18 +1420,20 @@
 Mutex AssetManager::SharedZip::gLock;
 DefaultKeyedVector<String8, wp<AssetManager::SharedZip> > AssetManager::SharedZip::gOpen;
 
-AssetManager::SharedZip::SharedZip(const String8& path, time_t modWhen)
-    : mPath(path), mZipFile(NULL), mModWhen(modWhen),
-      mResourceTableAsset(NULL), mResourceTable(NULL)
-{
-    if (kIsDebug) {
-        ALOGI("Creating SharedZip %p %s\n", this, mPath.c_str());
-    }
-    ALOGV("+++ opening zip '%s'\n", mPath.c_str());
-    mZipFile = ZipFileRO::open(mPath.c_str());
-    if (mZipFile == NULL) {
-        ALOGD("failed to open Zip archive '%s'\n", mPath.c_str());
-    }
+AssetManager::SharedZip::SharedZip(const String8& path, ModDate modWhen)
+    : mPath(path),
+      mZipFile(NULL),
+      mModWhen(modWhen),
+      mResourceTableAsset(NULL),
+      mResourceTable(NULL) {
+  if (kIsDebug) {
+    ALOGI("Creating SharedZip %p %s\n", this, mPath.c_str());
+  }
+  ALOGV("+++ opening zip '%s'\n", mPath.c_str());
+  mZipFile = ZipFileRO::open(mPath.c_str());
+  if (mZipFile == NULL) {
+    ALOGD("failed to open Zip archive '%s'\n", mPath.c_str());
+  }
 }
 
 AssetManager::SharedZip::SharedZip(int fd, const String8& path)
@@ -1453,7 +1455,7 @@
         bool createIfNotPresent)
 {
     AutoMutex _l(gLock);
-    time_t modWhen = getFileModDate(path.c_str());
+    auto modWhen = getFileModDate(path.c_str());
     sp<SharedZip> zip = gOpen.valueFor(path).promote();
     if (zip != NULL && zip->mModWhen == modWhen) {
         return zip;
@@ -1520,8 +1522,8 @@
 
 bool AssetManager::SharedZip::isUpToDate()
 {
-    time_t modWhen = getFileModDate(mPath.c_str());
-    return mModWhen == modWhen;
+  auto modWhen = getFileModDate(mPath.c_str());
+  return mModWhen == modWhen;
 }
 
 void AssetManager::SharedZip::addOverlay(const asset_path& ap)
diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h
index ce0985b..376c881 100644
--- a/libs/androidfw/include/androidfw/AssetManager.h
+++ b/libs/androidfw/include/androidfw/AssetManager.h
@@ -280,21 +280,21 @@
         ~SharedZip();
 
     private:
-        SharedZip(const String8& path, time_t modWhen);
-        SharedZip(int fd, const String8& path);
-        SharedZip(); // <-- not implemented
+     SharedZip(const String8& path, ModDate modWhen);
+     SharedZip(int fd, const String8& path);
+     SharedZip();  // <-- not implemented
 
-        String8 mPath;
-        ZipFileRO* mZipFile;
-        time_t mModWhen;
+     String8 mPath;
+     ZipFileRO* mZipFile;
+     ModDate mModWhen;
 
-        Asset* mResourceTableAsset;
-        ResTable* mResourceTable;
+     Asset* mResourceTableAsset;
+     ResTable* mResourceTable;
 
-        Vector<asset_path> mOverlays;
+     Vector<asset_path> mOverlays;
 
-        static Mutex gLock;
-        static DefaultKeyedVector<String8, wp<SharedZip> > gOpen;
+     static Mutex gLock;
+     static DefaultKeyedVector<String8, wp<SharedZip> > gOpen;
     };
 
     /*
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index e213fbd..ac75eb3 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -25,8 +25,9 @@
 #include "android-base/macros.h"
 #include "android-base/unique_fd.h"
 #include "androidfw/ConfigDescription.h"
-#include "androidfw/StringPiece.h"
 #include "androidfw/ResourceTypes.h"
+#include "androidfw/StringPiece.h"
+#include "androidfw/misc.h"
 #include "utils/ByteOrder.h"
 
 namespace android {
@@ -213,7 +214,7 @@
   android::base::unique_fd idmap_fd_;
   std::string_view overlay_apk_path_;
   std::string_view target_apk_path_;
-  time_t idmap_last_mod_time_;
+  ModDate idmap_last_mod_time_;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(LoadedIdmap);
diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h
index 077609d..c9ba8a0 100644
--- a/libs/androidfw/include/androidfw/misc.h
+++ b/libs/androidfw/include/androidfw/misc.h
@@ -13,14 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#pragma once
 
-#include <sys/types.h>
+#include <time.h>
 
 //
 // Handy utility functions and portability code.
 //
-#ifndef _LIBS_ANDROID_FW_MISC_H
-#define _LIBS_ANDROID_FW_MISC_H
 
 namespace android {
 
@@ -41,15 +40,35 @@
 } FileType;
 /* get the file's type; follows symlinks */
 FileType getFileType(const char* fileName);
-/* get the file's modification date; returns -1 w/errno set on failure */
-time_t getFileModDate(const char* fileName);
+
+// MinGW doesn't support nanosecond resolution in stat() modification time, and given
+// that it only matters on the device it's ok to keep it at a seconds level there.
+#ifdef _WIN32
+using ModDate = time_t;
+inline constexpr ModDate kInvalidModDate = ModDate(-1);
+inline constexpr unsigned long long kModDateResolutionNs = 1ull * 1000 * 1000 * 1000;
+inline time_t toTimeT(ModDate m) {
+  return m;
+}
+#else
+using ModDate = timespec;
+inline constexpr ModDate kInvalidModDate = {-1, -1};
+inline constexpr unsigned long long kModDateResolutionNs = 1;
+inline time_t toTimeT(ModDate m) {
+  return m.tv_sec;
+}
+#endif
+
+/* get the file's modification date; returns kInvalidModDate w/errno set on failure */
+ModDate getFileModDate(const char* fileName);
 /* same, but also returns -1 if the file has already been deleted */
-time_t getFileModDate(int fd);
+ModDate getFileModDate(int fd);
 
 // Check if |path| or |fd| resides on a readonly filesystem.
 bool isReadonlyFilesystem(const char* path);
 bool isReadonlyFilesystem(int fd);
 
-}; // namespace android
+}  // namespace android
 
-#endif // _LIBS_ANDROID_FW_MISC_H
+// Whoever uses getFileModDate() will need this as well
+bool operator==(const timespec& l, const timespec& r);
diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp
index 93dcaf5..32f3624 100644
--- a/libs/androidfw/misc.cpp
+++ b/libs/androidfw/misc.cpp
@@ -28,11 +28,13 @@
 #include <sys/vfs.h>
 #endif  // __linux__
 
-#include <cstring>
-#include <cstdio>
 #include <errno.h>
 #include <sys/stat.h>
 
+#include <cstdio>
+#include <cstring>
+#include <tuple>
+
 namespace android {
 
 /*
@@ -73,27 +75,34 @@
     }
 }
 
-/*
- * Get a file's modification date.
- */
-time_t getFileModDate(const char* fileName) {
-    struct stat sb;
-    if (stat(fileName, &sb) < 0) {
-        return (time_t)-1;
-    }
-    return sb.st_mtime;
+static ModDate getModDate(const struct stat& st) {
+#ifdef _WIN32
+  return st.st_mtime;
+#elif defined(__APPLE__)
+  return st.st_mtimespec;
+#else
+  return st.st_mtim;
+#endif
 }
 
-time_t getFileModDate(int fd) {
-    struct stat sb;
-    if (fstat(fd, &sb) < 0) {
-        return (time_t)-1;
-    }
-    if (sb.st_nlink <= 0) {
-        errno = ENOENT;
-        return (time_t)-1;
-    }
-    return sb.st_mtime;
+ModDate getFileModDate(const char* fileName) {
+  struct stat sb;
+  if (stat(fileName, &sb) < 0) {
+    return kInvalidModDate;
+  }
+  return getModDate(sb);
+}
+
+ModDate getFileModDate(int fd) {
+  struct stat sb;
+  if (fstat(fd, &sb) < 0) {
+    return kInvalidModDate;
+  }
+  if (sb.st_nlink <= 0) {
+    errno = ENOENT;
+    return kInvalidModDate;
+  }
+  return getModDate(sb);
 }
 
 #ifndef __linux__
@@ -124,4 +133,8 @@
 }
 #endif  // __linux__
 
-}; // namespace android
+}  // namespace android
+
+bool operator==(const timespec& l, const timespec& r) {
+  return std::tie(l.tv_sec, l.tv_nsec) == std::tie(r.tv_sec, l.tv_nsec);
+}
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
index 60aa7d8..cb2e56f 100644
--- a/libs/androidfw/tests/Idmap_test.cpp
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#include <chrono>
+#include <thread>
+
 #include "android-base/file.h"
 #include "androidfw/ApkAssets.h"
 #include "androidfw/AssetManager2.h"
@@ -27,6 +30,7 @@
 #include "data/overlayable/R.h"
 #include "data/system/R.h"
 
+using namespace std::chrono_literals;
 using ::testing::NotNull;
 
 namespace overlay = com::android::overlay;
@@ -218,10 +222,13 @@
 
   unlink(temp_file.path);
   ASSERT_FALSE(apk_assets->IsUpToDate());
-  sleep(2);
+
+  const auto sleep_duration =
+      std::chrono::nanoseconds(std::max(kModDateResolutionNs, 1'000'000ull));
+  std::this_thread::sleep_for(sleep_duration);
 
   base::WriteStringToFile("hello", temp_file.path);
-  sleep(2);
+  std::this_thread::sleep_for(sleep_duration);
 
   ASSERT_FALSE(apk_assets->IsUpToDate());
 }
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 09f40e0..f42017d 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -226,6 +226,10 @@
     @GuardedBy("mSessionLock")
     private final ArrayMap<String, MediaStreams> mOngoingMediaStreams = new ArrayMap<>();
 
+    @GuardedBy("mSessionLock")
+    private final ArrayMap<String, RoutingSessionInfo> mPendingSystemSessionReleases =
+            new ArrayMap<>();
+
     public MediaRoute2ProviderService() {
         mHandler = new Handler(Looper.getMainLooper());
     }
@@ -358,7 +362,9 @@
      * @return a {@link MediaStreams} instance that holds the media streams to route as part of the
      *     newly created routing session. May be null if system media capture failed, in which case
      *     you can ignore the return value, as you will receive a call to {@link #onReleaseSession}
-     *     where you can clean up this session
+     *     where you can clean up this session. {@link AudioRecord#startRecording()} must be called
+     *     immediately on {@link MediaStreams#getAudioRecord()} after calling this method, in order
+     *     to start streaming audio to the receiver.
      * @hide
      */
     // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
@@ -417,7 +423,7 @@
         }
 
         AudioFormat audioFormat = formats.mAudioFormat;
-        var mediaStreamsBuilder = new MediaStreams.Builder();
+        var mediaStreamsBuilder = new MediaStreams.Builder(sessionInfo);
         if (audioFormat != null) {
             populateAudioStream(audioFormat, uid, mediaStreamsBuilder);
         }
@@ -458,7 +464,6 @@
         if (uid != Process.INVALID_UID) {
             audioMixingRuleBuilder.addMixRule(AudioMixingRule.RULE_MATCH_UID, uid);
         }
-
         AudioMix mix =
                 new AudioMix.Builder(audioMixingRuleBuilder.build())
                         .setFormat(audioFormat)
@@ -471,7 +476,11 @@
             Log.e(TAG, "Couldn't fetch the audio manager.");
             return;
         }
-        audioManager.registerAudioPolicy(audioPolicy);
+        int audioPolicyResult = audioManager.registerAudioPolicy(audioPolicy);
+        if (audioPolicyResult != AudioManager.SUCCESS) {
+            Log.e(TAG, "Failed to register the audio policy.");
+            return;
+        }
         var audioRecord = audioPolicy.createAudioRecordSink(mix);
         if (audioRecord == null) {
             Log.e(TAG, "Audio record creation failed.");
@@ -521,8 +530,14 @@
         RoutingSessionInfo sessionInfo;
         synchronized (mSessionLock) {
             sessionInfo = mSessionInfos.remove(sessionId);
-            maybeReleaseMediaStreams(sessionId);
-
+            if (Flags.enableMirroringInMediaRouter2()) {
+                if (sessionInfo == null) {
+                    sessionInfo = maybeReleaseMediaStreams(sessionId);
+                }
+                if (sessionInfo == null) {
+                    sessionInfo = mPendingSystemSessionReleases.remove(sessionId);
+                }
+            }
             if (sessionInfo == null) {
                 Log.w(TAG, "notifySessionReleased: Ignoring unknown session info.");
                 return;
@@ -539,18 +554,26 @@
         }
     }
 
-    /** Releases any system media routing resources associated with the given {@code sessionId}. */
-    private void maybeReleaseMediaStreams(String sessionId) {
+    /**
+     * Releases any system media routing resources associated with the given {@code sessionId}.
+     *
+     * @return The {@link RoutingSessionInfo} that corresponds to the released media streams, or
+     *     null if no streams were released.
+     */
+    @Nullable
+    private RoutingSessionInfo maybeReleaseMediaStreams(String sessionId) {
         if (!Flags.enableMirroringInMediaRouter2()) {
-            return;
+            return null;
         }
         synchronized (mSessionLock) {
             var streams = mOngoingMediaStreams.remove(sessionId);
             if (streams != null) {
                 releaseAudioStream(streams.mAudioPolicy, streams.mAudioRecord);
                 // TODO: b/380431086: Release the video stream once implemented.
+                return streams.mSessionInfo;
             }
         }
+        return null;
     }
 
     // We cannot reach the code that requires MODIFY_AUDIO_ROUTING without holding it.
@@ -1019,12 +1042,16 @@
             if (!checkCallerIsSystem()) {
                 return;
             }
-            if (!checkSessionIdIsValid(sessionId, "releaseSession")) {
-                return;
+            synchronized (mSessionLock) {
+                // We proactively release the system media routing session resources when the
+                // system requests it, to ensure it happens immediately.
+                RoutingSessionInfo releasedSession = maybeReleaseMediaStreams(sessionId);
+                if (releasedSession != null) {
+                    mPendingSystemSessionReleases.put(sessionId, releasedSession);
+                } else if (!checkSessionIdIsValid(sessionId, "releaseSession")) {
+                    return;
+                }
             }
-            // We proactively release the system media routing once the system requests it, to
-            // ensure it happens immediately.
-            maybeReleaseMediaStreams(sessionId);
 
             addRequestId(requestId);
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession,
@@ -1047,9 +1074,19 @@
         @Nullable private final AudioPolicy mAudioPolicy;
         @Nullable private final AudioRecord mAudioRecord;
 
+        /**
+         * Holds the last {@link RoutingSessionInfo} associated with these streams.
+         *
+         * @hide
+         */
+        @GuardedBy("MediaRoute2ProviderService.this.mSessionLock")
+        @NonNull
+        private RoutingSessionInfo mSessionInfo;
+
         // TODO: b/380431086: Add the video equivalent.
 
         private MediaStreams(Builder builder) {
+            this.mSessionInfo = builder.mSessionInfo;
             this.mAudioPolicy = builder.mAudioPolicy;
             this.mAudioRecord = builder.mAudioRecord;
         }
@@ -1070,9 +1107,19 @@
          */
         public static final class Builder {
 
+            @NonNull private RoutingSessionInfo mSessionInfo;
             @Nullable private AudioPolicy mAudioPolicy;
             @Nullable private AudioRecord mAudioRecord;
 
+            /**
+             * Constructor.
+             *
+             * @param sessionInfo The {@link RoutingSessionInfo} associated with these streams.
+             */
+            Builder(@NonNull RoutingSessionInfo sessionInfo) {
+                mSessionInfo = requireNonNull(sessionInfo);
+            }
+
             /** Populates system media audio-related structures. */
             public Builder setAudioStream(
                     @NonNull AudioPolicy audioPolicy, @NonNull AudioRecord audioRecord) {
diff --git a/nfc/tests/src/android/nfc/tech/NfcATest.java b/nfc/tests/src/android/nfc/tech/NfcATest.java
new file mode 100644
index 0000000..40076eb
--- /dev/null
+++ b/nfc/tests/src/android/nfc/tech/NfcATest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.nfc.tech;
+
+import static android.nfc.tech.NfcA.EXTRA_ATQA;
+import static android.nfc.tech.NfcA.EXTRA_SAK;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.nfc.ErrorCodes;
+import android.nfc.INfcTag;
+import android.nfc.Tag;
+import android.nfc.TransceiveResult;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+
+public class NfcATest {
+    @Mock
+    private Tag mMockTag;
+    @Mock
+    private INfcTag mMockTagService;
+    @Mock
+    private Bundle mMockBundle;
+    private NfcA mNfcA;
+    private final byte[] mSampleArray = new byte[] {1, 2, 3};
+
+    @Before
+    public void setUp() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+        when(mMockBundle.getShort(EXTRA_SAK)).thenReturn((short) 1);
+        when(mMockBundle.getByteArray(EXTRA_ATQA)).thenReturn(mSampleArray);
+        when(mMockTag.getTechExtras(TagTechnology.NFC_A)).thenReturn(mMockBundle);
+
+        mNfcA = new NfcA(mMockTag);
+    }
+
+    @Test
+    public void testGetNfcAWithTech() {
+        Tag mockTag = mock(Tag.class);
+        when(mockTag.getTechExtras(TagTechnology.NFC_A)).thenReturn(mMockBundle);
+        when(mockTag.hasTech(TagTechnology.NFC_A)).thenReturn(true);
+
+        assertNotNull(NfcA.get(mockTag));
+        verify(mockTag).getTechExtras(TagTechnology.NFC_A);
+        verify(mockTag).hasTech(TagTechnology.NFC_A);
+    }
+
+    @Test
+    public void testGetNfcAWithoutTech() {
+        when(mMockTag.hasTech(TagTechnology.NFC_A)).thenReturn(false);
+        assertNull(NfcA.get(mMockTag));
+    }
+
+    @Test
+    public void testGetAtga() {
+        assertNotNull(mNfcA.getAtqa());
+    }
+
+    @Test
+    public void testGetSak() {
+        assertEquals((short) 1, mNfcA.getSak());
+    }
+
+    @Test
+    public void testTransceive() throws IOException, RemoteException {
+        TransceiveResult mockTransceiveResult = mock(TransceiveResult.class);
+        when(mMockTag.getConnectedTechnology()).thenReturn(TagTechnology.NFC_A);
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        when(mMockTag.getServiceHandle()).thenReturn(1);
+        when(mMockTagService.transceive(1, mSampleArray, true))
+                .thenReturn(mockTransceiveResult);
+        when(mockTransceiveResult.getResponseOrThrow()).thenReturn(mSampleArray);
+
+        mNfcA.transceive(mSampleArray);
+        verify(mMockTag).getTagService();
+        verify(mMockTag).getServiceHandle();
+    }
+
+    @Test
+    public void testGetMaxTransceiveLength() throws RemoteException {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        when(mMockTagService.getMaxTransceiveLength(TagTechnology.NFC_A)).thenReturn(1);
+
+        mNfcA.getMaxTransceiveLength();
+        verify(mMockTag).getTagService();
+    }
+
+    @Test
+    public void testSetTimeout() {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        try {
+            when(mMockTagService.setTimeout(TagTechnology.NFC_A, 1000)).thenReturn(
+                    ErrorCodes.SUCCESS);
+
+            mNfcA.setTimeout(1000);
+            verify(mMockTag).getTagService();
+            verify(mMockTagService).setTimeout(TagTechnology.NFC_A, 1000);
+        } catch (Exception e) {
+            fail("Unexpected exception during valid setTimeout: " + e.getMessage());
+        }
+    }
+
+    @Test
+    public void testSetTimeoutInvalidTimeout() {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        try {
+            when(mMockTagService.setTimeout(TagTechnology.NFC_A, -1)).thenReturn(
+                    ErrorCodes.ERROR_TIMEOUT);
+
+            assertThrows(IllegalArgumentException.class, () -> mNfcA.setTimeout(-1));
+        } catch (Exception e) {
+            fail("Unexpected exception during invalid setTimeout: " + e.getMessage());
+        }
+    }
+
+    @Test
+    public void testSetTimeoutRemoteException() {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        try {
+            when(mMockTagService.setTimeout(TagTechnology.NFC_A, 1000)).thenThrow(
+                    new RemoteException());
+
+            mNfcA.setTimeout(1000); // Should not throw an exception but log it
+            verify(mMockTag).getTagService();
+            verify(mMockTagService).setTimeout(TagTechnology.NFC_A, 1000);
+        } catch (Exception e) {
+            fail("Unexpected exception during RemoteException in setTimeout: " + e.getMessage());
+        }
+
+    }
+
+    @Test
+    public void testGetTimeout() {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        try {
+            when(mMockTagService.getTimeout(TagTechnology.NFC_A)).thenReturn(2000);
+
+            assertEquals(2000, mNfcA.getTimeout());
+            verify(mMockTag).getTagService();
+            verify(mMockTagService).getTimeout(TagTechnology.NFC_A);
+        } catch (Exception e) {
+            fail("Unexpected exception during valid getTimeout: " + e.getMessage());
+        }
+    }
+
+    @Test
+    public void testGetTimeoutRemoteException() {
+        when(mMockTag.getTagService()).thenReturn(mMockTagService);
+        try {
+            when(mMockTagService.getTimeout(TagTechnology.NFC_A)).thenThrow(new RemoteException());
+
+            assertEquals(0, mNfcA.getTimeout());
+        } catch (Exception e) {
+            fail("Unexpected exception during RemoteException in getTimeout: " + e.getMessage());
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index cf45231..c9aac91 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -21,6 +21,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.MainThread;
+import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -1643,7 +1644,7 @@
         CharSequence appLabel = "";
         ApplicationInfo appInfo = null;
         try {
-            int userId = UserHandle.getUserId(UserHandle.USER_CURRENT);
+            int userId = ActivityManager.getCurrentUser();
             appInfo = packageManager.getApplicationInfoAsUser(packageName, 0 /* flags */, userId);
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Failed to get app info", e);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 1c4def3..e01cb84 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -16,6 +16,19 @@
 
 package com.android.providers.settings;
 
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_DEVICE_SPECIFIC_CONFIG;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_GLOBAL;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_LOCALE;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_LOCK_SETTINGS;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_NETWORK_POLICIES;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SECURE;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SIM_SPECIFIC_SETTINGS;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SIM_SPECIFIC_SETTINGS_2;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SOFTAP_CONFIG;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SYSTEM;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_WIFI_NEW_CONFIG;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_WIFI_SETTINGS_BACKUP_DATA;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -99,22 +112,6 @@
     private static final int NULL_SIZE = -1;
     private static final float FONT_SCALE_DEF_VALUE = 1.0f;
 
-    private static final String KEY_SYSTEM = "system";
-    private static final String KEY_SECURE = "secure";
-    private static final String KEY_GLOBAL = "global";
-    private static final String KEY_LOCALE = "locale";
-    private static final String KEY_LOCK_SETTINGS = "lock_settings";
-    private static final String KEY_SOFTAP_CONFIG = "softap_config";
-    private static final String KEY_NETWORK_POLICIES = "network_policies";
-    private static final String KEY_WIFI_NEW_CONFIG = "wifi_new_config";
-    private static final String KEY_DEVICE_SPECIFIC_CONFIG = "device_specific_config";
-    private static final String KEY_SIM_SPECIFIC_SETTINGS = "sim_specific_settings";
-    // Restoring sim-specific data backed up from newer Android version to Android 12 was causing a
-    // fatal crash. Creating a backup with a different key will prevent Android 12 versions from
-    // restoring this data.
-    private static final String KEY_SIM_SPECIFIC_SETTINGS_2 = "sim_specific_settings_2";
-    private static final String KEY_WIFI_SETTINGS_BACKUP_DATA = "wifi_settings_backup_data";
-
     // Versioning of the state file.  Increment this version
     // number any time the set of state items is altered.
     private static final int STATE_VERSION = 9;
@@ -257,6 +254,7 @@
         mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
         if (com.android.server.backup.Flags.enableMetricsSettingsBackupAgents()) {
             mBackupRestoreEventLogger = this.getBackupRestoreEventLogger();
+            mSettingsHelper.setBackupRestoreEventLogger(mBackupRestoreEventLogger);
             numberOfSettingsPerKey = new HashMap<>();
             areAgentMetricsEnabled = true;
         }
@@ -412,9 +410,7 @@
                     mSettingsHelper
                         .setLocaleData(
                             localeData,
-                            size,
-                            mBackupRestoreEventLogger,
-                            KEY_LOCALE);
+                            size);
                     break;
 
                 case KEY_WIFI_CONFIG :
@@ -552,8 +548,7 @@
             if (nBytes > buffer.length) buffer = new byte[nBytes];
             in.readFully(buffer, 0, nBytes);
             mSettingsHelper
-                .setLocaleData(
-                    buffer, nBytes, mBackupRestoreEventLogger, KEY_LOCALE);
+                .setLocaleData(buffer, nBytes);
 
             // Restore older backups performing the necessary migrations.
             if (version < FULL_BACKUP_ADDED_WIFI_NEW) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupRestoreKeys.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupRestoreKeys.java
new file mode 100644
index 0000000..745c2fb
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupRestoreKeys.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 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.providers.settings;
+
+import android.net.Uri;
+import android.provider.Settings;
+
+/**
+ * Class to store the keys used for backup and restore.
+ */
+final class SettingsBackupRestoreKeys {
+    static final String KEY_UNKNOWN = "unknown";
+    static final String KEY_SYSTEM = "system";
+    static final String KEY_SECURE = "secure";
+    static final String KEY_GLOBAL = "global";
+    static final String KEY_LOCALE = "locale";
+    static final String KEY_LOCK_SETTINGS = "lock_settings";
+    static final String KEY_SOFTAP_CONFIG = "softap_config";
+    static final String KEY_NETWORK_POLICIES = "network_policies";
+    static final String KEY_WIFI_NEW_CONFIG = "wifi_new_config";
+    static final String KEY_DEVICE_SPECIFIC_CONFIG = "device_specific_config";
+    static final String KEY_SIM_SPECIFIC_SETTINGS = "sim_specific_settings";
+    // Restoring sim-specific data backed up from newer Android version to Android 12 was causing a
+    // fatal crash. Creating a backup with a different key will prevent Android 12 versions from
+    // restoring this data.
+    static final String KEY_SIM_SPECIFIC_SETTINGS_2 = "sim_specific_settings_2";
+    static final String KEY_WIFI_SETTINGS_BACKUP_DATA = "wifi_settings_backup_data";
+
+    /**
+     * Returns the key corresponding to the given URI.
+     *
+     * @param uri The URI of the setting's destination.
+     * @return The key corresponding to the given URI, or KEY_UNKNOWN if the URI is not recognized.
+     */
+    static String getKeyFromUri(Uri uri) {
+      if (uri.equals(Settings.Secure.CONTENT_URI)) {
+        return KEY_SECURE;
+      } else if (uri.equals(Settings.System.CONTENT_URI)) {
+        return KEY_SYSTEM;
+      } else if (uri.equals(Settings.Global.CONTENT_URI)) {
+        return KEY_GLOBAL;
+      } else {
+        return KEY_UNKNOWN;
+      }
+    }
+
+}
\ No newline at end of file
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 924c151..ab8d739 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -17,6 +17,7 @@
 package com.android.providers.settings;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.IActivityManager;
 import android.app.backup.BackupRestoreEventLogger;
@@ -84,6 +85,7 @@
     private Context mContext;
     private AudioManager mAudioManager;
     private TelephonyManager mTelephonyManager;
+    @Nullable private BackupRestoreEventLogger mBackupRestoreEventLogger;
 
     /**
      * A few settings elements are special in that a restore of those values needs to
@@ -741,11 +743,8 @@
      *
      * @param data the comma separated BCP-47 language tags in bytes.
      * @param size the size of the data in bytes.
-     * @param backupRestoreEventLogger the logger to log the restore event.
-     * @param dataType the data type of the setting for logging purposes.
      */
-    /* package */ void setLocaleData(
-        byte[] data, int size, BackupRestoreEventLogger backupRestoreEventLogger, String dataType) {
+    /* package */ void setLocaleData(byte[] data, int size) {
         final Configuration conf = mContext.getResources().getConfiguration();
 
         // Replace "_" with "-" to deal with older backups.
@@ -772,15 +771,15 @@
 
             am.updatePersistentConfigurationWithAttribution(config, mContext.getOpPackageName(),
                     mContext.getAttributionTag());
-            if (Flags.enableMetricsSettingsBackupAgents()) {
-                backupRestoreEventLogger
-                    .logItemsRestored(dataType, localeList.size());
+            if (Flags.enableMetricsSettingsBackupAgents() && mBackupRestoreEventLogger != null) {
+                mBackupRestoreEventLogger
+                    .logItemsRestored(SettingsBackupRestoreKeys.KEY_LOCALE, localeList.size());
             }
         } catch (RemoteException e) {
-            if (Flags.enableMetricsSettingsBackupAgents()) {
-                backupRestoreEventLogger
+            if (Flags.enableMetricsSettingsBackupAgents() && mBackupRestoreEventLogger != null) {
+                mBackupRestoreEventLogger
                     .logItemsRestoreFailed(
-                        dataType,
+                        SettingsBackupRestoreKeys.KEY_LOCALE,
                         localeList.size(),
                         ERROR_REMOTE_EXCEPTION_SETTING_LOCALE_DATA);
             }
@@ -795,4 +794,13 @@
         AudioManager am = new AudioManager(mContext);
         am.reloadAudioSettings();
     }
+
+    /**
+     * Sets the backup restore event logger.
+     *
+     * @param backupRestoreEventLogger the logger to log B&R metrics.
+     */
+    void setBackupRestoreEventLogger(BackupRestoreEventLogger backupRestoreEventLogger) {
+        mBackupRestoreEventLogger = backupRestoreEventLogger;
+    }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 55f48e3..f1f03c3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -120,6 +120,7 @@
 
 import com.android.internal.accessibility.util.AccessibilityUtils;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.display.RefreshRateSettingsUtils;
 import com.android.internal.os.BackgroundThread;
@@ -2914,6 +2915,14 @@
         };
     }
 
+    @VisibleForTesting
+    void injectServices(UserManager userManager, IPackageManager packageManager,
+            SystemConfigManager sysConfigManager) {
+        mUserManager = userManager;
+        mPackageManager = packageManager;
+        mSysConfigManager = sysConfigManager;
+    }
+
     private static final class Arguments {
         private static final Pattern WHERE_PATTERN_WITH_PARAM_NO_BRACKETS =
                 Pattern.compile("[\\s]*name[\\s]*=[\\s]*\\?[\\s]*");
@@ -3080,6 +3089,7 @@
 
         private static final String SSAID_USER_KEY = "userkey";
 
+        @GuardedBy("mLock")
         private final SparseArray<SettingsState> mSettingsStates = new SparseArray<>();
 
         private GenerationRegistry mGenerationRegistry;
@@ -3992,6 +4002,14 @@
             }
         }
 
+        @VisibleForTesting
+        void injectSettings(SettingsState settings, int type, int userId) {
+            int key = makeKey(type, userId);
+            synchronized (mLock) {
+                mSettingsStates.put(key, settings);
+            }
+        }
+
         private final class MyHandler extends Handler {
             private static final int MSG_NOTIFY_URI_CHANGED = 1;
             private static final int MSG_NOTIFY_DATA_CHANGED = 2;
@@ -4023,12 +4041,21 @@
             }
         }
 
-        private final class UpgradeController {
+        @VisibleForTesting
+        final class UpgradeController {
             private static final int SETTINGS_VERSION = 226;
 
             private final int mUserId;
 
+            private final Injector mInjector;
+
             public UpgradeController(int userId) {
+                this(/* injector= */ null, userId);
+            }
+
+            @VisibleForTesting
+            UpgradeController(Injector injector, int userId) {
+                mInjector = injector == null ? new Injector() : injector;
                 mUserId = userId;
             }
 
@@ -6136,8 +6163,8 @@
                             systemSettings.getSettingLocked(Settings.System.PEAK_REFRESH_RATE);
                     final Setting minRefreshRateSetting =
                             systemSettings.getSettingLocked(Settings.System.MIN_REFRESH_RATE);
-                    float highestRefreshRate = RefreshRateSettingsUtils
-                            .findHighestRefreshRateForDefaultDisplay(getContext());
+                    float highestRefreshRate =
+                            mInjector.findHighestRefreshRateForDefaultDisplay(getContext());
 
                     if (!peakRefreshRateSetting.isNull()) {
                         try {
@@ -6318,6 +6345,14 @@
             private long getBitMask(int capability) {
                 return 1 << (capability - 1);
             }
+
+            @VisibleForTesting
+            static class Injector {
+                float findHighestRefreshRateForDefaultDisplay(Context context) {
+                    return RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(
+                            context);
+                }
+            }
         }
 
         /**
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 5cd534e..bf3afed 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -107,7 +107,7 @@
  * the same lock to grab the current state to write to disk.
  * </p>
  */
-final class SettingsState {
+public class SettingsState {
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_PERSISTENCE = false;
 
@@ -1838,7 +1838,7 @@
         }
     }
 
-    class Setting {
+    public class Setting {
         private String name;
         private String value;
         private String defaultValue;
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupRestoreKeysTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupRestoreKeysTest.java
new file mode 100644
index 0000000..ef537e8
--- /dev/null
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupRestoreKeysTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 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.providers.settings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.Uri;
+import android.provider.Settings;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link SettingsBackupRestoreKeys}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SettingsBackupRestoreKeysTest {
+
+    @Test
+    public void getKeyFromUri_secureUri_returnsSecureKey() {
+        assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Settings.Secure.CONTENT_URI))
+                .isEqualTo(SettingsBackupRestoreKeys.KEY_SECURE);
+    }
+
+    @Test
+    public void getKeyFromUri_systemUri_returnsSystemKey() {
+        assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Settings.System.CONTENT_URI))
+                .isEqualTo(SettingsBackupRestoreKeys.KEY_SYSTEM);
+    }
+
+    @Test
+    public void getKeyFromUri_globalUri_returnsGlobalKey() {
+        assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Settings.Global.CONTENT_URI))
+                .isEqualTo(SettingsBackupRestoreKeys.KEY_GLOBAL);
+    }
+
+    @Test
+    public void getKeyFromUri_unknownUri_returnsUnknownKey() {
+        assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Uri.parse("content://unknown")))
+                .isEqualTo(SettingsBackupRestoreKeys.KEY_UNKNOWN);
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
index 9cce431..119b287 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
@@ -16,7 +16,7 @@
 
 package com.android.providers.settings;
 
-import static android.provider.Settings.Secure.ACCESSIBILITY_ENABLED;
+import static android.provider.Settings.Secure.CONTENT_CAPTURE_ENABLED;
 import static android.provider.Settings.Secure.SYNC_PARENT_SOUNDS;
 import static android.provider.Settings.System.RINGTONE;
 
@@ -67,7 +67,7 @@
     private static final String SPACE_SYSTEM = "system";
     private static final String SPACE_SECURE = "secure";
 
-    private static final String CLONE_TO_MANAGED_PROFILE_SETTING = ACCESSIBILITY_ENABLED;
+    private static final String CLONE_TO_MANAGED_PROFILE_SETTING = CONTENT_CAPTURE_ENABLED;
     private static final String CLONE_FROM_PARENT_SETTINGS = RINGTONE;
     private static final String SYNC_FROM_PARENT_SETTINGS = SYNC_PARENT_SOUNDS;
 
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/UpgradeControllerTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/UpgradeControllerTest.java
new file mode 100644
index 0000000..26ff376
--- /dev/null
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/UpgradeControllerTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.providers.settings;
+
+import static android.provider.Settings.System.MIN_REFRESH_RATE;
+import static android.provider.Settings.System.PEAK_REFRESH_RATE;
+
+import static com.android.providers.settings.SettingsProvider.SETTINGS_TYPE_GLOBAL;
+import static com.android.providers.settings.SettingsProvider.SETTINGS_TYPE_SECURE;
+import static com.android.providers.settings.SettingsProvider.SETTINGS_TYPE_SYSTEM;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.IPackageManager;
+import android.os.Looper;
+import android.os.SystemConfigManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class UpgradeControllerTest {
+    private static final int USER_ID = UserHandle.USER_SYSTEM;
+    private static final float HIGHEST_REFRESH_RATE = 130f;
+
+    private final Context mContext =
+            spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+    private final SettingsProvider.SettingsRegistry.UpgradeController.Injector mInjector =
+            new SettingsProvider.SettingsRegistry.UpgradeController.Injector() {
+                @Override
+                float findHighestRefreshRateForDefaultDisplay(Context context) {
+                    return HIGHEST_REFRESH_RATE;
+                }
+            };
+    private final SettingsProvider mSettingsProvider = new SettingsProvider() {
+        @Override
+        public boolean onCreate() {
+            return true;
+        }
+    };
+    private final SettingsProvider.SettingsRegistry mSettingsRegistry =
+            mSettingsProvider.new SettingsRegistry(Looper.getMainLooper());
+    private final SettingsProvider.SettingsRegistry.UpgradeController mUpgradeController =
+            mSettingsRegistry.new UpgradeController(mInjector, USER_ID);
+
+    @Mock
+    private UserManager mUserManager;
+
+    @Mock
+    private IPackageManager mPackageManager;
+
+    @Mock
+    private SystemConfigManager mSysConfigManager;
+
+    @Mock
+    private SettingsState mSystemSettings;
+
+    @Mock
+    private SettingsState mSecureSettings;
+
+    @Mock
+    private SettingsState mGlobalSettings;
+
+    @Mock
+    private SettingsState.Setting mMockSetting;
+
+    @Mock
+    private SettingsState.Setting mPeakRefreshRateSetting;
+
+    @Mock
+    private SettingsState.Setting mMinRefreshRateSetting;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mSettingsProvider.attachInfoForTesting(mContext, /* info= */ null);
+        mSettingsProvider.injectServices(mUserManager, mPackageManager, mSysConfigManager);
+        when(mSystemSettings.getSettingLocked(any())).thenReturn(mMockSetting);
+        when(mSecureSettings.getSettingLocked(any())).thenReturn(mMockSetting);
+        when(mGlobalSettings.getSettingLocked(any())).thenReturn(mMockSetting);
+        when(mMockSetting.isNull()).thenReturn(true);
+        when(mMockSetting.getValue()).thenReturn("0");
+
+        when(mSystemSettings.getSettingLocked(PEAK_REFRESH_RATE))
+                .thenReturn(mPeakRefreshRateSetting);
+        when(mSystemSettings.getSettingLocked(MIN_REFRESH_RATE))
+                .thenReturn(mMinRefreshRateSetting);
+
+        mSettingsRegistry.injectSettings(mSystemSettings, SETTINGS_TYPE_SYSTEM, USER_ID);
+        mSettingsRegistry.injectSettings(mSecureSettings, SETTINGS_TYPE_SECURE, USER_ID);
+        mSettingsRegistry.injectSettings(mGlobalSettings, SETTINGS_TYPE_GLOBAL, USER_ID);
+
+        // Lowest version so that all upgrades are run
+        when(mSecureSettings.getVersionLocked()).thenReturn(118);
+    }
+
+    @Test
+    public void testUpgrade_refreshRateSettings_defaultValues() {
+        when(mPeakRefreshRateSetting.isNull()).thenReturn(true);
+        when(mMinRefreshRateSetting.isNull()).thenReturn(true);
+
+        mUpgradeController.upgradeIfNeededLocked();
+
+        // Should remain unchanged
+        verify(mSystemSettings, never()).insertSettingLocked(eq(PEAK_REFRESH_RATE),
+                /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(),
+                /* packageName= */ any());
+        verify(mSystemSettings, never()).insertSettingLocked(eq(MIN_REFRESH_RATE),
+                /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(),
+                /* packageName= */ any());
+    }
+
+    @Test
+    public void testUpgrade_refreshRateSettings_enabled() {
+        when(mPeakRefreshRateSetting.isNull()).thenReturn(false);
+        when(mMinRefreshRateSetting.isNull()).thenReturn(false);
+        when(mPeakRefreshRateSetting.getValue()).thenReturn(String.valueOf(HIGHEST_REFRESH_RATE));
+        when(mMinRefreshRateSetting.getValue()).thenReturn(String.valueOf(HIGHEST_REFRESH_RATE));
+
+        mUpgradeController.upgradeIfNeededLocked();
+
+        // Highest refresh rate gets converted to infinity
+        verify(mSystemSettings).insertSettingLocked(eq(PEAK_REFRESH_RATE),
+                eq(String.valueOf(Float.POSITIVE_INFINITY)), /* tag= */ any(),
+                /* makeDefault= */ anyBoolean(), /* packageName= */ any());
+        verify(mSystemSettings).insertSettingLocked(eq(MIN_REFRESH_RATE),
+                eq(String.valueOf(Float.POSITIVE_INFINITY)), /* tag= */ any(),
+                /* makeDefault= */ anyBoolean(), /* packageName= */ any());
+    }
+
+    @Test
+    public void testUpgrade_refreshRateSettings_disabled() {
+        when(mPeakRefreshRateSetting.isNull()).thenReturn(false);
+        when(mMinRefreshRateSetting.isNull()).thenReturn(false);
+        when(mPeakRefreshRateSetting.getValue()).thenReturn("70f");
+        when(mMinRefreshRateSetting.getValue()).thenReturn("70f");
+
+        mUpgradeController.upgradeIfNeededLocked();
+
+        // Should remain unchanged
+        verify(mSystemSettings, never()).insertSettingLocked(eq(PEAK_REFRESH_RATE),
+                /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(),
+                /* packageName= */ any());
+        verify(mSystemSettings, never()).insertSettingLocked(eq(MIN_REFRESH_RATE),
+                /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(),
+                /* packageName= */ any());
+    }
+}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 3d250fd..0600fb3 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -85,6 +85,7 @@
 filegroup {
     name: "SystemUI-tests-broken-robofiles-run",
     srcs: [
+        "tests/src/**/systemui/dreams/touch/CommunalTouchHandlerTest.java",
         "tests/src/**/systemui/shade/NotificationShadeWindowViewControllerTest.kt",
         "tests/src/**/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt",
         "tests/src/**/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt",
@@ -418,6 +419,9 @@
         "androidx.slice_slice-view",
     ],
     manifest: "AndroidManifest-res.xml",
+    flags_packages: [
+        "com_android_systemui_flags",
+    ],
 }
 
 android_library {
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 1b1c91d..c4d13ba 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1229,6 +1229,14 @@
 }
 
 flag {
+  name: "glanceable_hub_v2_resources"
+  namespace: "systemui"
+  description: "Read only flag for rolling out glanceable hub v2 resource values"
+  bug: "375689917"
+  is_fixed_read_only: true
+}
+
+flag {
     name: "dream_overlay_updated_font"
     namespace: "systemui"
     description: "Flag to enable updated font settings for dream overlay"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index a17a1d4..0a0003e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -19,17 +19,20 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntRect
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.zIndex
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
 import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
 import com.android.systemui.communal.ui.compose.section.CommunalPopupSection
@@ -39,8 +42,11 @@
 import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
 import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import javax.inject.Inject
+import kotlin.math.min
+import kotlin.math.roundToInt
 
 /** Renders the content of the glanceable hub. */
 class CommunalContent
@@ -48,6 +54,7 @@
 constructor(
     private val viewModel: CommunalViewModel,
     private val interactionHandler: SmartspaceInteractionHandler,
+    private val communalSettingsInteractor: CommunalSettingsInteractor,
     private val dialogFactory: SystemUIDialogFactory,
     private val lockSection: LockSection,
     private val bottomAreaSection: BottomAreaSection,
@@ -77,11 +84,20 @@
                             sceneScope = this@Content,
                         )
                     }
-                    with(lockSection) {
-                        LockIcon(
-                            overrideColor = MaterialTheme.colorScheme.onPrimaryContainer,
+                    if (communalSettingsInteractor.isV2FlagEnabled()) {
+                        Icon(
+                            painter = painterResource(id = R.drawable.ic_lock),
+                            contentDescription = null,
+                            tint = MaterialTheme.colorScheme.onPrimaryContainer,
                             modifier = Modifier.element(Communal.Elements.LockIcon),
                         )
+                    } else {
+                        with(lockSection) {
+                            LockIcon(
+                                overrideColor = MaterialTheme.colorScheme.onPrimaryContainer,
+                                modifier = Modifier.element(Communal.Elements.LockIcon),
+                            )
+                        }
                     }
                     with(bottomAreaSection) {
                         IndicationArea(
@@ -98,14 +114,42 @@
 
                 val noMinConstraints = constraints.copy(minWidth = 0, minHeight = 0)
 
-                val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+                val lockIconPlaceable =
+                    if (communalSettingsInteractor.isV2FlagEnabled()) {
+                        val lockIconSizeInt = lockIconSize.roundToPx()
+                        lockIconMeasurable.measure(
+                            Constraints.fixed(width = lockIconSizeInt, height = lockIconSizeInt)
+                        )
+                    } else {
+                        lockIconMeasurable.measure(noMinConstraints)
+                    }
                 val lockIconBounds =
-                    IntRect(
-                        left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
-                        top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
-                        right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
-                        bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
-                    )
+                    if (communalSettingsInteractor.isV2FlagEnabled()) {
+                        val lockIconDistanceFromBottom =
+                            min(
+                                (constraints.maxHeight * lockIconPercentDistanceFromBottom)
+                                    .roundToInt(),
+                                lockIconMinDistanceFromBottom.roundToPx(),
+                            )
+                        val x = constraints.maxWidth / 2 - lockIconPlaceable.width / 2
+                        val y =
+                            constraints.maxHeight -
+                                lockIconDistanceFromBottom -
+                                lockIconPlaceable.height
+                        IntRect(
+                            left = x,
+                            top = y,
+                            right = x + lockIconPlaceable.width,
+                            bottom = y + lockIconPlaceable.height,
+                        )
+                    } else {
+                        IntRect(
+                            left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+                            top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+                            right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+                            bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+                        )
+                    }
 
                 val bottomAreaPlaceable = bottomAreaMeasurable.measure(noMinConstraints)
 
@@ -129,12 +173,17 @@
 
                     val bottomAreaTop = constraints.maxHeight - bottomAreaPlaceable.height
                     bottomAreaPlaceable.place(x = 0, y = bottomAreaTop)
+
+                    val screensaverButtonPaddingInt = screensaverButtonPadding.roundToPx()
                     screensaverButtonPlaceable?.place(
                         x =
                             constraints.maxWidth -
                                 screensaverButtonSizeInt -
-                                Dimensions.ItemSpacing.roundToPx(),
-                        y = lockIconBounds.top,
+                                screensaverButtonPaddingInt,
+                        y =
+                            constraints.maxHeight -
+                                screensaverButtonSizeInt -
+                                screensaverButtonPaddingInt,
                     )
                 }
             }
@@ -142,6 +191,12 @@
     }
 
     companion object {
-        val screensaverButtonSize: Dp = 64.dp
+        private val screensaverButtonSize: Dp = 64.dp
+        private val screensaverButtonPadding: Dp = 24.dp
+        // TODO(b/382739998): Remove these hardcoded values once lock icon size and bottom area
+        // position are sorted.
+        private val lockIconSize: Dp = 54.dp
+        private val lockIconPercentDistanceFromBottom = 0.1f
+        private val lockIconMinDistanceFromBottom = 70.dp
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 5c7ca97..0344ab8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -38,7 +38,6 @@
 import com.android.compose.modifiers.thenIf
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
 import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
@@ -90,14 +89,10 @@
 ) {
 
     init {
-        if (!MigrateClocksToBlueprint.isEnabled) {
-            throw IllegalStateException("this requires MigrateClocksToBlueprint.isEnabled")
-        }
         // This scene container section moves the NSSL to the SharedNotificationContainer.
         // This also requires that SharedNotificationContainer gets moved to the
         // SceneWindowRootView by the SceneWindowRootViewBinder. Prior to Scene Container,
-        // but when the KeyguardShadeMigrationNssl flag is enabled, NSSL is moved into this
-        // container by the NotificationStackScrollLayoutSection.
+        // NSSL is moved into this container by the NotificationStackScrollLayoutSection.
         // Ensure stackScrollLayout is a child of sharedNotificationContainer.
 
         if (stackScrollLayout.parent != sharedNotificationContainer) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index 8a5c96d..f821e42 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -20,6 +20,9 @@
 
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.rememberScrollableState
+import androidx.compose.foundation.gestures.scrollable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.ExperimentalLayoutApi
 import androidx.compose.foundation.layout.PaddingValues
@@ -41,16 +44,21 @@
 import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ReadOnlyComposable
+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.graphics.Color
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.LowestZIndexContentPicker
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.effect.rememberOffsetOverscrollEffect
 import com.android.compose.windowsizeclass.LocalWindowSizeClass
 import com.android.systemui.res.R
 
@@ -61,7 +69,22 @@
     modifier: Modifier = Modifier,
     content: @Composable () -> Unit,
 ) {
-    Box(modifier) {
+    // TODO(b/384653288) This should be removed when b/378470603 is done.
+    val idleEffect = rememberOffsetOverscrollEffect(Orientation.Vertical)
+    Box(
+        modifier
+            .overscroll(idleEffect)
+            .nestedScroll(
+                remember {
+                    object : NestedScrollConnection {
+                        override suspend fun onPreFling(available: Velocity): Velocity {
+                            return available
+                        }
+                    }
+                }
+            )
+            .scrollable(rememberScrollableState { 0f }, Orientation.Vertical, idleEffect)
+    ) {
         Scrim(onClicked = onScrimClicked)
 
         Box(modifier = Modifier.fillMaxSize().panelPadding(), contentAlignment = Alignment.TopEnd) {
@@ -180,7 +203,6 @@
 
     object Dimensions {
         val PanelCornerRadius = 46.dp
-        val OverscrollLimit = 32.dp
     }
 
     object Shapes {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index fa5f72b..1480db9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -37,12 +37,14 @@
 import androidx.compose.material3.Slider
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.semantics.CustomAccessibilityAction
@@ -66,6 +68,10 @@
 import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
 import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderState
+import kotlin.math.round
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
 
 @Composable
 fun VolumeSlider(
@@ -196,9 +202,17 @@
                 )
             }
         }
-
-    // Perform haptics due to UI composition
-    hapticsViewModel?.onValueChange(value)
+    var lastDiscreteStep by remember { mutableFloatStateOf(round(value)) }
+    LaunchedEffect(value) {
+        snapshotFlow { value }
+            .map { round(it) }
+            .filter { it != lastDiscreteStep }
+            .distinctUntilChanged()
+            .collect { discreteStep ->
+                lastDiscreteStep = discreteStep
+                hapticsViewModel?.onValueChange(discreteStep)
+            }
+    }
 
     PlatformSlider(
         modifier =
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 801a2d6..b76656d 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -71,7 +71,6 @@
         }
 
     var hasCustomPositionUpdatedAnimation: Boolean = false
-    var migratedClocks: Boolean = false
 
     private val time = Calendar.getInstance()
 
@@ -228,11 +227,7 @@
     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
         logger.d("onMeasure")
 
-        if (
-            migratedClocks &&
-                !isSingleLineInternal &&
-                MeasureSpec.getMode(heightMeasureSpec) == EXACTLY
-        ) {
+        if (!isSingleLineInternal && MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) {
             // Call straight into TextView.setTextSize to avoid setting lastUnconstrainedTextSize
             val size = min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F)
             super.setTextSize(COMPLEX_UNIT_PX, size)
@@ -248,7 +243,7 @@
                     }
             }
 
-        if (migratedClocks && hasCustomPositionUpdatedAnimation) {
+        if (hasCustomPositionUpdatedAnimation) {
             // Expand width to avoid clock being clipped during stepping animation
             val targetWidth = measuredWidth + MeasureSpec.getSize(widthMeasureSpec) / 2
 
@@ -582,12 +577,10 @@
     }
 
     override fun onRtlPropertiesChanged(layoutDirection: Int) {
-        if (migratedClocks) {
-            if (layoutDirection == LAYOUT_DIRECTION_RTL) {
-                textAlignment = TEXT_ALIGNMENT_TEXT_END
-            } else {
-                textAlignment = TEXT_ALIGNMENT_TEXT_START
-            }
+        if (layoutDirection == LAYOUT_DIRECTION_RTL) {
+            textAlignment = TEXT_ALIGNMENT_TEXT_END
+        } else {
+            textAlignment = TEXT_ALIGNMENT_TEXT_START
         }
         super.onRtlPropertiesChanged(layoutDirection)
     }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index ad9eba8..74d595c 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -20,7 +20,6 @@
 import android.icu.text.NumberFormat
 import android.util.TypedValue
 import android.view.LayoutInflater
-import android.view.View
 import android.widget.FrameLayout
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.customization.R
@@ -55,7 +54,6 @@
     private val layoutInflater: LayoutInflater,
     private val resources: Resources,
     private val settings: ClockSettings?,
-    private val migratedClocks: Boolean = false,
     messageBuffers: ClockMessageBuffers? = null,
 ) : ClockController {
     override val smallClock: DefaultClockFaceController
@@ -67,7 +65,6 @@
     private val burmeseLineSpacing =
         resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale_burmese)
     private val defaultLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale)
-    protected var onSecondaryDisplay: Boolean = false
 
     override val events: DefaultClockEvents
     override val config: ClockConfig by lazy {
@@ -175,10 +172,7 @@
                     recomputePadding(targetRegion)
                 }
 
-                override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {
-                    this@DefaultClockController.onSecondaryDisplay = onSecondaryDisplay
-                    recomputePadding(null)
-                }
+                override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {}
             }
 
         open fun recomputePadding(targetRegion: Rect?) {}
@@ -197,32 +191,11 @@
         override val config = ClockFaceConfig(hasCustomPositionUpdatedAnimation = true)
 
         init {
-            view.migratedClocks = migratedClocks
             view.hasCustomPositionUpdatedAnimation = true
             animations = LargeClockAnimations(view, 0f, 0f)
         }
 
-        override fun recomputePadding(targetRegion: Rect?) {
-            if (migratedClocks) {
-                return
-            }
-            // We center the view within the targetRegion instead of within the parent
-            // view by computing the difference and adding that to the padding.
-            val lp = view.getLayoutParams() as FrameLayout.LayoutParams
-            lp.topMargin =
-                if (onSecondaryDisplay) {
-                    // On the secondary display we don't want any additional top/bottom margin.
-                    0
-                } else {
-                    val parent = view.parent
-                    val yDiff =
-                        if (targetRegion != null && parent is View && parent.isLaidOut())
-                            targetRegion.centerY() - parent.height / 2f
-                        else 0f
-                    (-0.5f * view.bottom + yDiff).toInt()
-                }
-            view.setLayoutParams(lp)
-        }
+        override fun recomputePadding(targetRegion: Rect?) {}
 
         /** See documentation at [AnimatableClockView.offsetGlyphsForStepClockAnimation]. */
         fun offsetGlyphsForStepClockAnimation(fromLeft: Int, direction: Int, fraction: Float) {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index e898725..c73e1c3 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -47,7 +47,6 @@
     val ctx: Context,
     val layoutInflater: LayoutInflater,
     val resources: Resources,
-    private val migratedClocks: Boolean = false,
     private val isClockReactiveVariantsEnabled: Boolean = false,
 ) : ClockProvider {
     private var messageBuffers: ClockMessageBuffers? = null
@@ -83,14 +82,7 @@
                 FLEX_DESIGN,
             )
         } else {
-            DefaultClockController(
-                ctx,
-                layoutInflater,
-                resources,
-                settings,
-                migratedClocks,
-                messageBuffers,
-            )
+            DefaultClockController(ctx, layoutInflater, resources, settings, messageBuffers)
         }
     }
 
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
index 21d41ae..4a47f1b 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
@@ -140,8 +140,8 @@
         }
 
         /**
-         * targetRegion passed to all customized clock applies counter translationY of
-         * KeyguardStatusView and keyguard_large_clock_top_margin from default clock
+         * targetRegion passed to all customized clock applies counter translationY of Keyguard and
+         * keyguard_large_clock_top_margin from default clock
          */
         override fun onTargetRegionChanged(targetRegion: Rect?) {
             // When a clock needs to be aligned with screen, like weather clock
diff --git a/packages/SystemUI/docs/clock-plugins.md b/packages/SystemUI/docs/clock-plugins.md
index fee82df..813038e 100644
--- a/packages/SystemUI/docs/clock-plugins.md
+++ b/packages/SystemUI/docs/clock-plugins.md
@@ -43,12 +43,6 @@
 SystemUI event dispatchers to the clock controllers. It maintains a set of event listeners, but
 otherwise attempts to do as little work as possible. It does maintain some state where necessary.
 
-[KeyguardClockSwitchController](../src/com/android/keyguard/KeyguardClockSwitchController.java) is
-the primary controller for the [KeyguardClockSwitch](../src/com/android/keyguard/KeyguardClockSwitch.java),
-which serves as the view parent within SystemUI. Together they ensure the correct clock (either
-large or small) is shown, handle animation between clock sizes, and control some sizing/layout
-parameters for the clocks.
-
 ### Creating a custom clock
 In order to create a custom clock, a partner must:
  - Write an implementation of ClockProviderPlugin and the subinterfaces relevant to your use-case.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
index fa5af51..77e3869 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
@@ -127,6 +127,7 @@
     @Test
     @DisableFlags(
         Flags.FLAG_COMMUNAL_HUB,
+        Flags.FLAG_GLANCEABLE_HUB_V2,
         Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX,
         Flags.FLAG_SCENE_CONTAINER,
     )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
similarity index 97%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
index 08f139c..9c9d5ad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
@@ -51,7 +51,7 @@
                     title = title,
                     subtitle = subtitle,
                     description = description,
-                    contentView = contentView
+                    contentView = contentView,
                 ),
                 BiometricUserInfo(USER_ID),
                 BiometricOperationInfo(OPERATION_ID),
@@ -101,9 +101,7 @@
         val fpPros = fingerprintSensorPropertiesInternal().first()
         val request =
             BiometricPromptRequest.Biometric(
-                promptInfo(
-                    logoBitmap = logoBitmap,
-                ),
+                promptInfo(logoBitmap = logoBitmap),
                 BiometricUserInfo(USER_ID),
                 BiometricOperationInfo(OPERATION_ID),
                 BiometricModalities(fingerprintProperties = fpPros),
@@ -162,7 +160,7 @@
                     BiometricUserInfo(USER_ID),
                     BiometricOperationInfo(OPERATION_ID),
                     stealth,
-                )
+                ),
             )
 
         for (request in toCheck) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt
index 370adee..03bf79b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt
@@ -19,6 +19,7 @@
 import android.app.StatsManager
 import android.app.StatsManager.StatsPullAtomCallback
 import android.content.pm.UserInfo
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.util.StatsEvent
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -32,6 +33,7 @@
 import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
 import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.settings.fakeUserTracker
 import com.android.systemui.shared.system.SysUiStatsLog
@@ -75,10 +77,7 @@
         // Set up an existing user, which is required for widgets to show
         val userInfos = listOf(UserInfo(0, "main", UserInfo.FLAG_MAIN))
         userRepository.setUserInfos(userInfos)
-        userTracker.set(
-            userInfos = userInfos,
-            selectedUserIndex = 0,
-        )
+        userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
 
         underTest =
             CommunalMetricsStartable(
@@ -90,14 +89,16 @@
             )
     }
 
+    @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2)
     @Test
-    fun start_communalFlagDisabled_doNotSetPullAtomCallback() {
-        kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
+    fun start_communalFlagDisabled_doNotSetPullAtomCallback() =
+        kosmos.runTest {
+            fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
 
-        underTest.start()
+            underTest.start()
 
-        verify(statsManager, never()).setPullAtomCallback(anyInt(), anyOrNull(), any(), any())
-    }
+            verify(statsManager, never()).setPullAtomCallback(anyInt(), anyOrNull(), any(), any())
+        }
 
     @Test
     fun onPullAtom_atomTagDoesNotMatch_pullSkip() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
index b66727e..038ea9c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
@@ -26,8 +26,8 @@
 import android.os.UserManager.USER_TYPE_PROFILE_MANAGED
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
 import android.provider.Settings
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
@@ -35,10 +35,13 @@
 import com.android.systemui.broadcast.broadcastDispatcher
 import com.android.systemui.communal.data.model.DisabledReason
 import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_BACKGROUND_SETTING
+import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
 import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.nullable
@@ -51,15 +54,21 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.eq
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply { mainResources = mContext.orCreateTestableResources.resources }
     private val testScope = kosmos.testScope
     private lateinit var underTest: CommunalSettingsRepository
 
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags!!)
+    }
+
     @Before
     fun setUp() {
         kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -164,17 +173,17 @@
             assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_INVALID_USER)
         }
 
-    @EnableFlags(FLAG_COMMUNAL_HUB)
+    @EnableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun classicFlagIsDisabled() =
-        testScope.runTest {
-            kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
+        kosmos.runTest {
+            setCommunalV2Enabled(false)
             val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
             assertThat(enabledState?.enabled).isFalse()
             assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG)
         }
 
-    @DisableFlags(FLAG_COMMUNAL_HUB)
+    @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun communalHubFlagIsDisabled() =
         testScope.runTest {
@@ -295,6 +304,34 @@
             }
         }
 
+    @Test
+    fun screensaverDisabledByUser() =
+        testScope.runTest {
+            val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER))
+
+            kosmos.fakeSettings.putIntForUser(
+                Settings.Secure.SCREENSAVER_ENABLED,
+                0,
+                PRIMARY_USER.id,
+            )
+
+            assertThat(enabledState).isFalse()
+        }
+
+    @Test
+    fun screensaverEnabledByUser() =
+        testScope.runTest {
+            val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER))
+
+            kosmos.fakeSettings.putIntForUser(
+                Settings.Secure.SCREENSAVER_ENABLED,
+                1,
+                PRIMARY_USER.id,
+            )
+
+            assertThat(enabledState).isTrue()
+        }
+
     private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
         whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
             .thenReturn(disabledFlags)
@@ -310,5 +347,11 @@
         val SECONDARY_USER = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
         val WORK_PROFILE =
             UserInfo(10, "work", /* iconPath= */ "", /* flags= */ 0, USER_TYPE_PROFILE_MANAGED)
+
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf(FLAG_GLANCEABLE_HUB_V2)
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index b9e646f..7ae0577 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.Flags.FLAG_COMMUNAL_RESPONSIVE_GRID
 import com.android.systemui.Flags.FLAG_COMMUNAL_WIDGET_RESIZING
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.broadcastDispatcher
 import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
@@ -232,7 +233,7 @@
     @Test
     fun isCommunalAvailable_communalDisabled_false() =
         testScope.runTest {
-            mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB)
+            mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
 
             val isAvailable by collectLastValue(underTest.isCommunalAvailable)
             assertThat(isAvailable).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt
index 8820685..b780808 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.communal.ui.viewmodel
 
 import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
 import android.service.dream.dreamManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -29,12 +30,16 @@
 import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.plugins.activityStarter
 import com.android.systemui.statusbar.policy.batteryController
 import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mockito.verify
 import org.mockito.kotlin.any
 import org.mockito.kotlin.whenever
@@ -56,10 +61,9 @@
     }
 
     @Test
-    fun shouldShowDreamButtonOnHub_trueWhenCanDream() =
+    fun shouldShowDreamButtonOnHub_trueWhenPluggedIn() =
         with(kosmos) {
             runTest {
-                whenever(dreamManager.canStartDreaming(any())).thenReturn(true)
                 whenever(batteryController.isPluggedIn()).thenReturn(true)
 
                 val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub)
@@ -68,22 +72,9 @@
         }
 
     @Test
-    fun shouldShowDreamButtonOnHub_falseWhenCannotDream() =
-        with(kosmos) {
-            runTest {
-                whenever(dreamManager.canStartDreaming(any())).thenReturn(false)
-                whenever(batteryController.isPluggedIn()).thenReturn(true)
-
-                val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub)
-                assertThat(shouldShowButton).isFalse()
-            }
-        }
-
-    @Test
     fun shouldShowDreamButtonOnHub_falseWhenNotPluggedIn() =
         with(kosmos) {
             runTest {
-                whenever(dreamManager.canStartDreaming(any())).thenReturn(true)
                 whenever(batteryController.isPluggedIn()).thenReturn(false)
 
                 val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub)
@@ -92,13 +83,40 @@
         }
 
     @Test
-    fun onShowDreamButtonTap_startsDream() =
+    fun onShowDreamButtonTap_dreamsEnabled_startsDream() =
         with(kosmos) {
             runTest {
+                val currentUser = fakeUserRepository.asMainUser()
+                kosmos.fakeSettings.putIntForUser(
+                    Settings.Secure.SCREENSAVER_ENABLED,
+                    1,
+                    currentUser.id,
+                )
+                runCurrent()
+
                 underTest.onShowDreamButtonTap()
                 runCurrent()
 
                 verify(dreamManager).startDream()
             }
         }
+
+    @Test
+    fun onShowDreamButtonTap_dreamsDisabled_startsActivity() =
+        with(kosmos) {
+            runTest {
+                val currentUser = fakeUserRepository.asMainUser()
+                kosmos.fakeSettings.putIntForUser(
+                    Settings.Secure.SCREENSAVER_ENABLED,
+                    0,
+                    currentUser.id,
+                )
+                runCurrent()
+
+                underTest.onShowDreamButtonTap()
+                runCurrent()
+
+                verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt())
+            }
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ControlActionCoordinatorImplTest.kt
similarity index 80%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ControlActionCoordinatorImplTest.kt
index 9285146..c8661cf5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ControlActionCoordinatorImplTest.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.controls.ui
 
+import android.view.HapticFeedbackConstants
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import android.view.HapticFeedbackConstants
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.controls.ControlsMetricsLogger
@@ -29,6 +29,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.wm.shell.taskview.TaskViewFactory
+import java.util.Optional
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -45,31 +46,20 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
-import java.util.Optional
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class ControlActionCoordinatorImplTest : SysuiTestCase() {
-    @Mock
-    private lateinit var vibratorHelper: VibratorHelper
-    @Mock
-    private lateinit var keyguardStateController: KeyguardStateController
-    @Mock
-    private lateinit var bgExecutor: DelayableExecutor
-    @Mock
-    private lateinit var uiExecutor: DelayableExecutor
-    @Mock
-    private lateinit var activityStarter: ActivityStarter
-    @Mock
-    private lateinit var broadcastSender: BroadcastSender
-    @Mock
-    private lateinit var taskViewFactory: Optional<TaskViewFactory>
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-    private lateinit var cvh: ControlViewHolder
-    @Mock
-    private lateinit var metricsLogger: ControlsMetricsLogger
-    @Mock
-    private lateinit var controlsSettingsDialogManager: ControlsSettingsDialogManager
+    @Mock private lateinit var vibratorHelper: VibratorHelper
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var bgExecutor: DelayableExecutor
+    @Mock private lateinit var uiExecutor: DelayableExecutor
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var broadcastSender: BroadcastSender
+    @Mock private lateinit var taskViewFactory: Optional<TaskViewFactory>
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var cvh: ControlViewHolder
+    @Mock private lateinit var metricsLogger: ControlsMetricsLogger
+    @Mock private lateinit var controlsSettingsDialogManager: ControlsSettingsDialogManager
 
     companion object {
         fun <T> any(): T = Mockito.any<T>()
@@ -89,18 +79,21 @@
         controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
         controlsSettingsRepository.setCanShowControlsInLockscreen(true)
 
-        coordinator = spy(ControlActionCoordinatorImpl(
-                mContext,
-                bgExecutor,
-                uiExecutor,
-                activityStarter,
-                broadcastSender,
-                keyguardStateController,
-                taskViewFactory,
-                metricsLogger,
-                vibratorHelper,
-                controlsSettingsRepository,
-        ))
+        coordinator =
+            spy(
+                ControlActionCoordinatorImpl(
+                    mContext,
+                    bgExecutor,
+                    uiExecutor,
+                    activityStarter,
+                    broadcastSender,
+                    keyguardStateController,
+                    taskViewFactory,
+                    metricsLogger,
+                    vibratorHelper,
+                    controlsSettingsRepository,
+                )
+            )
         coordinator.activityContext = mContext
 
         `when`(cvh.cws.ci.controlId).thenReturn(ID)
@@ -198,19 +191,15 @@
     fun drag_isEdge_performsSegmentTickHaptics() {
         coordinator.drag(cvh, true)
 
-        verify(vibratorHelper).performHapticFeedback(
-            any(),
-            eq(HapticFeedbackConstants.SEGMENT_TICK)
-        )
+        verify(vibratorHelper)
+            .performHapticFeedback(any(), eq(HapticFeedbackConstants.SEGMENT_TICK))
     }
 
     @Test
     fun drag_isNotEdge_performsFrequentTickHaptics() {
         coordinator.drag(cvh, false)
 
-        verify(vibratorHelper).performHapticFeedback(
-            any(),
-            eq(HapticFeedbackConstants.SEGMENT_FREQUENT_TICK)
-        )
+        verify(vibratorHelper)
+            .performHapticFeedback(any(), eq(HapticFeedbackConstants.SEGMENT_FREQUENT_TICK))
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index b07097d..5921e94 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -92,10 +92,10 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Mockito
 import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.isNull
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.firstValue
@@ -768,7 +768,7 @@
             runCurrent()
             verify(mDreamOverlayCallback).onRedirectWake(true)
             client.onWakeRequested()
-            verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), any(), isNull())
+            verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), any(), anyOrNull())
             verify(mUiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START)
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/domain/interactor/HomeControlsComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/domain/interactor/HomeControlsComponentInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java
index 004aeb0..3d9fb0a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java
@@ -56,6 +56,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.util.DeviceConfigProxyFake;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -84,6 +85,7 @@
     FakeExecutor mMainExecutor;
     FakeExecutor mBackgroundExecutor;
     DeviceConfigProxyFake mDeviceConfigProxyFake;
+    FakeShadeDialogContextInteractor mFakeShadeDialogContextInteractor;
 
     @Mock
     IActivityManager mIActivityManager;
@@ -118,6 +120,7 @@
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
 
+        mFakeShadeDialogContextInteractor = new FakeShadeDialogContextInteractor(mContext);
         mDeviceConfigProxyFake = new DeviceConfigProxyFake();
         mSystemClock = new FakeSystemClock();
         mMainExecutor = new FakeExecutor(mSystemClock);
@@ -346,7 +349,6 @@
                 SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
                 "true", false);
         FgsManagerController fmc = new FgsManagerControllerImpl(
-                mContext,
                 mContext.getResources(),
                 mMainExecutor,
                 mBackgroundExecutor,
@@ -359,7 +361,8 @@
                 mDialogTransitionAnimator,
                 mBroadcastDispatcher,
                 mDumpManager,
-                mSystemUIDialogFactory
+                mSystemUIDialogFactory,
+                mFakeShadeDialogContextInteractor
         );
         fmc.init();
         Assert.assertTrue(fmc.getIncludesUserVisibleJobs());
@@ -374,7 +377,6 @@
                 SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
                 "false", false);
         fmc = new FgsManagerControllerImpl(
-                mContext,
                 mContext.getResources(),
                 mMainExecutor,
                 mBackgroundExecutor,
@@ -387,7 +389,8 @@
                 mDialogTransitionAnimator,
                 mBroadcastDispatcher,
                 mDumpManager,
-                mSystemUIDialogFactory
+                mSystemUIDialogFactory,
+                mFakeShadeDialogContextInteractor
         );
         fmc.init();
         Assert.assertFalse(fmc.getIncludesUserVisibleJobs());
@@ -487,7 +490,6 @@
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
 
         FgsManagerController result = new FgsManagerControllerImpl(
-                mContext,
                 mContext.getResources(),
                 mMainExecutor,
                 mBackgroundExecutor,
@@ -500,7 +502,8 @@
                 mDialogTransitionAnimator,
                 mBroadcastDispatcher,
                 mDumpManager,
-                mSystemUIDialogFactory
+                mSystemUIDialogFactory,
+                mFakeShadeDialogContextInteractor
         );
         result.init();
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
index fbbdc46..0e823cc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
 import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.DataSaverController
 import com.android.systemui.util.mockito.whenever
@@ -96,6 +97,7 @@
                 dataSaverController,
                 mDialogTransitionAnimator,
                 systemUIDialogFactory,
+                FakeShadeDialogContextInteractor(mContext),
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
index b888617..5c6657b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
@@ -8,6 +8,7 @@
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -231,7 +232,8 @@
 
         mViewHolder.onWifiClick(mWifiEntry, mock(View.class));
 
-        verify(mSpyContext).startActivity(any());
+        verify(mInternetDialogController).startActivityForDialog(any());
+        verify(mSpyContext, never()).startActivity(any());
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index 039a1dc..518a0a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.qs.PseudoGridView
 import com.android.systemui.qs.QSUserSwitcherEvent
 import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
@@ -84,6 +85,7 @@
                 mDialogTransitionAnimator,
                 uiEventLogger,
                 dialogFactory,
+                FakeShadeDialogContextInteractor(mContext),
             )
     }
 
@@ -91,32 +93,32 @@
     fun showDialog_callsDialogShow() {
         val launchController = mock<DialogTransitionAnimator.Controller>()
         `when`(launchExpandable.dialogTransitionController(any())).thenReturn(launchController)
-        controller.showDialog(context, launchExpandable)
+        controller.showDialog(launchExpandable)
         verify(mDialogTransitionAnimator).show(eq(dialog), eq(launchController), anyBoolean())
         verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN)
     }
 
     @Test
     fun dialog_showForAllUsers() {
-        controller.showDialog(context, launchExpandable)
+        controller.showDialog(launchExpandable)
         verify(dialog).setShowForAllUsers(true)
     }
 
     @Test
     fun dialog_cancelOnTouchOutside() {
-        controller.showDialog(context, launchExpandable)
+        controller.showDialog(launchExpandable)
         verify(dialog).setCanceledOnTouchOutside(true)
     }
 
     @Test
     fun adapterAndGridLinked() {
-        controller.showDialog(context, launchExpandable)
+        controller.showDialog(launchExpandable)
         verify(userDetailViewAdapter).linkToViewGroup(any<PseudoGridView>())
     }
 
     @Test
     fun doneButtonLogsCorrectly() {
-        controller.showDialog(context, launchExpandable)
+        controller.showDialog(launchExpandable)
 
         verify(dialog).setPositiveButton(anyInt(), capture(clickCaptor))
 
@@ -129,7 +131,7 @@
     fun clickSettingsButton_noFalsing_opensSettings() {
         `when`(falsingManager.isFalseTap(anyInt())).thenReturn(false)
 
-        controller.showDialog(context, launchExpandable)
+        controller.showDialog(launchExpandable)
 
         verify(dialog)
             .setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */)
@@ -150,7 +152,7 @@
     fun clickSettingsButton_Falsing_notOpensSettings() {
         `when`(falsingManager.isFalseTap(anyInt())).thenReturn(true)
 
-        controller.showDialog(context, launchExpandable)
+        controller.showDialog(launchExpandable)
 
         verify(dialog)
             .setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index e56b965..3187cca6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -37,7 +37,6 @@
 import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.bouncerSceneContentViewModel
-import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.EnableSceneContainer
@@ -48,9 +47,9 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.collectLastValue
 import com.android.systemui.kosmos.currentValue
-import com.android.systemui.kosmos.runCurrent
 import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.verifyCurrent
 import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
@@ -77,12 +76,9 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.advanceTimeBy
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.verify
 
 /**
  * Integration test cases for the Scene Framework.
@@ -137,10 +133,10 @@
             sceneContainerViewModel.activateIn(testScope)
 
             assertWithMessage("Initial scene key mismatch!")
-                .that(sceneContainerViewModel.currentScene.value)
+                .that(currentValue(sceneContainerViewModel.currentScene))
                 .isEqualTo(sceneContainerConfig.initialSceneKey)
             assertWithMessage("Initial scene container visibility mismatch!")
-                .that(sceneContainerViewModel.isVisible)
+                .that(currentValue { sceneContainerViewModel.isVisible })
                 .isTrue()
         }
 
@@ -337,7 +333,6 @@
                 .that(bouncerActionButton)
                 .isNotNull()
             kosmos.bouncerSceneContentViewModel.onActionButtonClicked(bouncerActionButton!!)
-            runCurrent()
 
             // TODO(b/369765704): Assert that an activity was started once we use ActivityStarter.
         }
@@ -358,9 +353,8 @@
                 .that(bouncerActionButton)
                 .isNotNull()
             kosmos.bouncerSceneContentViewModel.onActionButtonClicked(bouncerActionButton!!)
-            runCurrent()
 
-            verify(mockTelecomManager).showInCallScreen(any())
+            verifyCurrent(mockTelecomManager).showInCallScreen(any())
         }
 
     @Test
@@ -413,7 +407,7 @@
      * the UI must gradually transition between scenes.
      */
     private fun Kosmos.getCurrentSceneInUi(): SceneKey {
-        return when (val state = transitionState.value) {
+        return when (val state = currentValue(transitionState)) {
             is ObservableTransitionState.Idle -> state.currentScene
             is ObservableTransitionState.Transition.ChangeScene -> state.fromScene
             is ObservableTransitionState.Transition.ShowOrHideOverlay -> state.currentScene
@@ -436,7 +430,6 @@
         // is not an observable that can trigger a new evaluation.
         fakeDeviceEntryRepository.setLockscreenEnabled(enableLockscreen)
         fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
-        testScope.runCurrent()
     }
 
     /** Emulates a phone call in progress. */
@@ -447,7 +440,6 @@
             setIsInCall(true)
             setCallState(TelephonyManager.CALL_STATE_OFFHOOK)
         }
-        testScope.runCurrent()
     }
 
     /**
@@ -480,24 +472,21 @@
                 isInitiatedByUserInput = false,
                 isUserInputOngoing = flowOf(false),
             )
-        testScope.runCurrent()
 
         // Report progress of transition.
-        while (progressFlow.value < 1f) {
+        while (currentValue(progressFlow) < 1f) {
             progressFlow.value += 0.2f
-            testScope.runCurrent()
         }
 
         // End the transition and report the change.
         transitionState.value = ObservableTransitionState.Idle(to)
 
         fakeSceneDataSource.unpause(force = true)
-        testScope.runCurrent()
 
         assertWithMessage("Visibility mismatch after scene transition from $from to $to!")
-            .that(sceneContainerViewModel.isVisible)
+            .that(currentValue { sceneContainerViewModel.isVisible })
             .isEqualTo(expectedVisible)
-        assertThat(sceneContainerViewModel.currentScene.value).isEqualTo(to)
+        assertThat(currentValue(sceneContainerViewModel.currentScene)).isEqualTo(to)
 
         bouncerSceneJob =
             if (to == Scenes.Bouncer) {
@@ -510,7 +499,6 @@
                 bouncerSceneJob?.cancel()
                 null
             }
-        testScope.runCurrent()
     }
 
     /**
@@ -556,13 +544,12 @@
         )
 
         powerInteractor.setAwakeForTest()
-        testScope.runCurrent()
     }
 
     /** Unlocks the device by entering the correct PIN. Ends up in the Gone scene. */
     private fun Kosmos.unlockDevice() {
         assertWithMessage("Cannot unlock a device that's already unlocked!")
-            .that(deviceEntryInteractor.isUnlocked.value)
+            .that(currentValue(deviceEntryInteractor.isUnlocked))
             .isFalse()
 
         emulateUserDrivenTransition(Scenes.Bouncer)
@@ -595,7 +582,6 @@
             pinBouncerViewModel.onPinButtonClicked(digit)
         }
         pinBouncerViewModel.onAuthenticateButtonClicked()
-        testScope.runCurrent()
     }
 
     /**
@@ -625,26 +611,23 @@
         }
         pinBouncerViewModel.onAuthenticateButtonClicked()
         fakeMobileConnectionsRepository.isAnySimSecure.value = false
-        testScope.runCurrent()
 
         setAuthMethod(authMethodAfterSimUnlock, enableLockscreen)
-        testScope.runCurrent()
     }
 
     /** Changes device wakefulness state from asleep to awake, going through intermediary states. */
     private fun Kosmos.wakeUpDevice() {
-        val wakefulnessModel = powerInteractor.detailedWakefulness.value
+        val wakefulnessModel = currentValue(powerInteractor.detailedWakefulness)
         assertWithMessage("Cannot wake up device as it's already awake!")
             .that(wakefulnessModel.isAwake())
             .isFalse()
 
         powerInteractor.setAwakeForTest()
-        testScope.runCurrent()
     }
 
     /** Changes device wakefulness state from awake to asleep, going through intermediary states. */
     private suspend fun Kosmos.putDeviceToSleep(waitForLock: Boolean = true) {
-        val wakefulnessModel = powerInteractor.detailedWakefulness.value
+        val wakefulnessModel = currentValue(powerInteractor.detailedWakefulness)
         assertWithMessage("Cannot put device to sleep as it's already asleep!")
             .that(wakefulnessModel.isAwake())
             .isTrue()
@@ -659,22 +642,18 @@
                     )
                     .toLong()
             )
-        } else {
-            testScope.runCurrent()
         }
     }
 
     /** Emulates the dismissal of the IME (soft keyboard). */
     private fun Kosmos.dismissIme() {
-        (bouncerSceneContentViewModel.authMethodViewModel.value as? PasswordBouncerViewModel)?.let {
-            it.onImeDismissed()
-            testScope.runCurrent()
-        }
+        (currentValue(bouncerSceneContentViewModel.authMethodViewModel)
+                as? PasswordBouncerViewModel)
+            ?.let { it.onImeDismissed() }
     }
 
     private fun Kosmos.introduceLockedSim() {
         setAuthMethod(AuthenticationMethodModel.Sim)
         fakeMobileConnectionsRepository.isAnySimSecure.value = true
-        testScope.runCurrent()
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 929537dc..0361ffe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -212,18 +212,6 @@
     }
 
     @Test
-    @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    fun testDragDownHelperCalledWhenDraggingDown() =
-        testScope.runTest {
-            whenever(dragDownHelper.isDraggingDown).thenReturn(true)
-            val now = SystemClock.elapsedRealtime()
-            val ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0f, 0f, 0 /* meta */)
-            underTest.onTouchEvent(ev)
-            verify(dragDownHelper).onTouchEvent(ev)
-            ev.recycle()
-        }
-
-    @Test
     fun testNoInterceptTouch() =
         testScope.runTest {
             captureInteractionEventHandler()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index aa8b4f1..4423426 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -117,7 +117,6 @@
         verify(mockLargeClockView).onTimeZoneChanged(notNull())
         verify(mockSmallClockView).refreshTime()
         verify(mockLargeClockView).refreshTime()
-        verify(mockLargeClockView).setLayoutParams(any())
     }
 
     @Test
@@ -163,7 +162,6 @@
         clock.largeClock.events.onFontSettingChanged(200f)
 
         verify(mockLargeClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), eq(200f))
-        verify(mockLargeClockView).setLayoutParams(any())
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
similarity index 74%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
index abd0a28..3359db0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
@@ -43,7 +43,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class PromotedNotificationContentExtractorTest : SysuiTestCase() {
+class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
     private val kosmos = testKosmos()
 
     private val provider =
@@ -54,7 +54,7 @@
     @Test
     @DisableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
     fun shouldNotExtract_bothFlagsDisabled() {
-        val notif = createEntry().also { provider.promotedEntries.add(it) }
+        val notif = createEntry()
         val content = extractContent(notif)
         assertThat(content).isNull()
     }
@@ -63,7 +63,7 @@
     @EnableFlags(PromotedNotificationUi.FLAG_NAME)
     @DisableFlags(StatusBarNotifChips.FLAG_NAME)
     fun shouldExtract_promotedNotificationUiFlagEnabled() {
-        val entry = createEntry().also { provider.promotedEntries.add(it) }
+        val entry = createEntry()
         val content = extractContent(entry)
         assertThat(content).isNotNull()
     }
@@ -72,7 +72,7 @@
     @EnableFlags(StatusBarNotifChips.FLAG_NAME)
     @DisableFlags(PromotedNotificationUi.FLAG_NAME)
     fun shouldExtract_statusBarNotifChipsFlagEnabled() {
-        val entry = createEntry().also { provider.promotedEntries.add(it) }
+        val entry = createEntry()
         val content = extractContent(entry)
         assertThat(content).isNotNull()
     }
@@ -80,7 +80,7 @@
     @Test
     @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
     fun shouldExtract_bothFlagsEnabled() {
-        val entry = createEntry().also { provider.promotedEntries.add(it) }
+        val entry = createEntry()
         val content = extractContent(entry)
         assertThat(content).isNotNull()
     }
@@ -88,22 +88,19 @@
     @Test
     @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
     fun shouldNotExtract_providerDidNotPromote() {
-        val entry = createEntry().also { provider.promotedEntries.remove(it) }
+        val entry = createEntry(promoted = false)
         val content = extractContent(entry)
         assertThat(content).isNull()
     }
 
     @Test
     @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
-    fun extractContent_commonFields() {
-        val entry =
-            createEntry {
-                    setSubText(TEST_SUB_TEXT)
-                    setContentTitle(TEST_CONTENT_TITLE)
-                    setContentText(TEST_CONTENT_TEXT)
-                    setShortCriticalText(TEST_SHORT_CRITICAL_TEXT)
-                }
-                .also { provider.promotedEntries.add(it) }
+    fun extractsContent_commonFields() {
+        val entry = createEntry {
+            setSubText(TEST_SUB_TEXT)
+            setContentTitle(TEST_CONTENT_TITLE)
+            setContentText(TEST_CONTENT_TEXT)
+        }
 
         val content = extractContent(entry)
 
@@ -117,9 +114,7 @@
     @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
     @DisableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
     fun extractContent_apiFlagOff_shortCriticalTextNotExtracted() {
-        val entry =
-            createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) }
-                .also { provider.promotedEntries.add(it) }
+        val entry = createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) }
 
         val content = extractContent(entry)
 
@@ -134,9 +129,7 @@
         android.app.Flags.FLAG_API_RICH_ONGOING,
     )
     fun extractContent_apiFlagOn_shortCriticalTextExtracted() {
-        val entry =
-            createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) }
-                .also { provider.promotedEntries.add(it) }
+        val entry = createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) }
 
         val content = extractContent(entry)
 
@@ -151,7 +144,7 @@
         android.app.Flags.FLAG_API_RICH_ONGOING,
     )
     fun extractContent_noShortCriticalTextSet_textIsNull() {
-        val entry = createEntry {}.also { provider.promotedEntries.add(it) }
+        val entry = createEntry { setShortCriticalText(null) }
 
         val content = extractContent(entry)
 
@@ -161,9 +154,8 @@
 
     @Test
     @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
-    fun extractContent_fromBigPictureStyle() {
-        val entry =
-            createEntry { setStyle(BigPictureStyle()) }.also { provider.promotedEntries.add(it) }
+    fun extractsContent_fromBigPictureStyle() {
+        val entry = createEntry { setStyle(BigPictureStyle()) }
 
         val content = extractContent(entry)
 
@@ -174,8 +166,7 @@
     @Test
     @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
     fun extractContent_fromBigTextStyle() {
-        val entry =
-            createEntry { setStyle(BigTextStyle()) }.also { provider.promotedEntries.add(it) }
+        val entry = createEntry { setStyle(BigTextStyle()) }
 
         val content = extractContent(entry)
 
@@ -187,11 +178,13 @@
     @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
     fun extractContent_fromCallStyle() {
         val hangUpIntent =
-            PendingIntent.getBroadcast(context, 0, Intent("hangup"), PendingIntent.FLAG_IMMUTABLE)
-
-        val entry =
-            createEntry { setStyle(CallStyle.forOngoingCall(TEST_PERSON, hangUpIntent)) }
-                .also { provider.promotedEntries.add(it) }
+            PendingIntent.getBroadcast(
+                context,
+                0,
+                Intent("hangup_action"),
+                PendingIntent.FLAG_IMMUTABLE,
+            )
+        val entry = createEntry { setStyle(CallStyle.forOngoingCall(TEST_PERSON, hangUpIntent)) }
 
         val content = extractContent(entry)
 
@@ -202,11 +195,9 @@
     @Test
     @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
     fun extractContent_fromProgressStyle() {
-        val entry =
-            createEntry {
-                    setStyle(ProgressStyle().addProgressSegment(Segment(100)).setProgress(75))
-                }
-                .also { provider.promotedEntries.add(it) }
+        val entry = createEntry {
+            setStyle(ProgressStyle().addProgressSegment(Segment(100)).setProgress(75))
+        }
 
         val content = extractContent(entry)
 
@@ -220,13 +211,9 @@
     @Test
     @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
     fun extractContent_fromIneligibleStyle() {
-        val entry =
-            createEntry {
-                    setStyle(
-                        MessagingStyle(TEST_PERSON).addMessage("message text", 0L, TEST_PERSON)
-                    )
-                }
-                .also { provider.promotedEntries.add(it) }
+        val entry = createEntry {
+            setStyle(MessagingStyle(TEST_PERSON).addMessage("message text", 0L, TEST_PERSON))
+        }
 
         val content = extractContent(entry)
 
@@ -239,9 +226,14 @@
         return underTest.extractContent(entry, recoveredBuilder)
     }
 
-    private fun createEntry(builderBlock: Notification.Builder.() -> Unit = {}): NotificationEntry {
-        val notif = Notification.Builder(context, "a").also(builderBlock).build()
-        return NotificationEntryBuilder().setNotification(notif).build()
+    private fun createEntry(
+        promoted: Boolean = true,
+        builderBlock: Notification.Builder.() -> Unit = {},
+    ): NotificationEntry {
+        val notif = Notification.Builder(context, "channel").also(builderBlock).build()
+        return NotificationEntryBuilder().setNotification(notif).build().also {
+            provider.shouldPromoteForEntry[it] = promoted
+        }
     }
 
     companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 6eb2764..cdc8bc1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -30,7 +30,6 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -61,7 +60,7 @@
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
+import com.android.systemui.statusbar.notification.promoted.FakePromotedNotificationContentExtractor;
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
@@ -105,7 +104,8 @@
     @Mock private NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider;
     @Mock private HeadsUpStyleProvider mHeadsUpStyleProvider;
     @Mock private NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;
-    @Mock private PromotedNotificationContentExtractor mPromotedNotificationContentExtractor;
+    private final FakePromotedNotificationContentExtractor mPromotedNotificationContentExtractor =
+            new FakePromotedNotificationContentExtractor();
 
     private final SmartReplyStateInflater mSmartReplyStateInflater =
             new SmartReplyStateInflater() {
@@ -395,12 +395,11 @@
     public void testExtractsPromotedContent_notWhenBothFlagsDisabled() throws Exception {
         final PromotedNotificationContentModel content =
                 new PromotedNotificationContentModel.Builder("key").build();
-        when(mPromotedNotificationContentExtractor.extractContent(any(), any()))
-                .thenReturn(content);
+        mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content);
 
         inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
 
-        verify(mPromotedNotificationContentExtractor, never()).extractContent(any(), any());
+        mPromotedNotificationContentExtractor.verifyZeroExtractCalls();
     }
 
     @Test
@@ -410,12 +409,11 @@
             throws Exception {
         final PromotedNotificationContentModel content =
                 new PromotedNotificationContentModel.Builder("key").build();
-        when(mPromotedNotificationContentExtractor.extractContent(any(), any()))
-                .thenReturn(content);
+        mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content);
 
         inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
 
-        verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any());
+        mPromotedNotificationContentExtractor.verifyOneExtractCall();
         assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel());
     }
 
@@ -425,12 +423,11 @@
     public void testExtractsPromotedContent_whenStatusBarNotifChipsFlagEnabled() throws Exception {
         final PromotedNotificationContentModel content =
                 new PromotedNotificationContentModel.Builder("key").build();
-        when(mPromotedNotificationContentExtractor.extractContent(any(), any()))
-                .thenReturn(content);
+        mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content);
 
         inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
 
-        verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any());
+        mPromotedNotificationContentExtractor.verifyOneExtractCall();
         assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel());
     }
 
@@ -439,23 +436,22 @@
     public void testExtractsPromotedContent_whenBothFlagsEnabled() throws Exception {
         final PromotedNotificationContentModel content =
                 new PromotedNotificationContentModel.Builder("key").build();
-        when(mPromotedNotificationContentExtractor.extractContent(any(), any()))
-                .thenReturn(content);
+        mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content);
 
         inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
 
-        verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any());
+        mPromotedNotificationContentExtractor.verifyOneExtractCall();
         assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel());
     }
 
     @Test
     @EnableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME})
     public void testExtractsPromotedContent_null() throws Exception {
-        when(mPromotedNotificationContentExtractor.extractContent(any(), any())).thenReturn(null);
+        mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), null);
 
         inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
 
-        verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any());
+        mPromotedNotificationContentExtractor.verifyOneExtractCall();
         assertNull(mRow.getEntry().getPromotedNotificationContentModel());
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index 1851799..9fb72fb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -37,7 +37,7 @@
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor
+import com.android.systemui.statusbar.notification.promoted.FakePromotedNotificationContentExtractor
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams
@@ -67,7 +67,6 @@
 import org.mockito.kotlin.any
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
-import org.mockito.kotlin.never
 import org.mockito.kotlin.spy
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
@@ -110,7 +109,7 @@
                 return inflatedSmartReplyState
             }
         }
-    private val promotedNotificationContentExtractor: PromotedNotificationContentExtractor = mock()
+    private val promotedNotificationContentExtractor = FakePromotedNotificationContentExtractor()
 
     @Before
     fun setUp() {
@@ -234,7 +233,7 @@
                 packageContext = mContext,
                 remoteViews = NewRemoteViews(),
                 contentModel = NotificationContentModel(headsUpStatusBarModel),
-                extractedPromotedNotificationContentModel = null,
+                promotedContent = null,
             )
         val countDownLatch = CountDownLatch(1)
         NotificationRowContentBinderImpl.applyRemoteView(
@@ -475,12 +474,11 @@
     @DisableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
     fun testExtractsPromotedContent_notWhenBothFlagsDisabled() {
         val content = PromotedNotificationContentModel.Builder("key").build()
-        whenever(promotedNotificationContentExtractor.extractContent(any(), any()))
-            .thenReturn(content)
+        promotedNotificationContentExtractor.resetForEntry(row.entry, content)
 
         inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
 
-        verify(promotedNotificationContentExtractor, never()).extractContent(any(), any())
+        promotedNotificationContentExtractor.verifyZeroExtractCalls()
     }
 
     @Test
@@ -488,12 +486,11 @@
     @DisableFlags(StatusBarNotifChips.FLAG_NAME)
     fun testExtractsPromotedContent_whenPromotedNotificationUiFlagEnabled() {
         val content = PromotedNotificationContentModel.Builder("key").build()
-        whenever(promotedNotificationContentExtractor.extractContent(any(), any()))
-            .thenReturn(content)
+        promotedNotificationContentExtractor.resetForEntry(row.entry, content)
 
         inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
 
-        verify(promotedNotificationContentExtractor, times(1)).extractContent(any(), any())
+        promotedNotificationContentExtractor.verifyOneExtractCall()
         Assert.assertEquals(content, row.entry.promotedNotificationContentModel)
     }
 
@@ -502,12 +499,11 @@
     @DisableFlags(PromotedNotificationUi.FLAG_NAME)
     fun testExtractsPromotedContent_whenStatusBarNotifChipsFlagEnabled() {
         val content = PromotedNotificationContentModel.Builder("key").build()
-        whenever(promotedNotificationContentExtractor.extractContent(any(), any()))
-            .thenReturn(content)
+        promotedNotificationContentExtractor.resetForEntry(row.entry, content)
 
         inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
 
-        verify(promotedNotificationContentExtractor, times(1)).extractContent(any(), any())
+        promotedNotificationContentExtractor.verifyOneExtractCall()
         Assert.assertEquals(content, row.entry.promotedNotificationContentModel)
     }
 
@@ -515,15 +511,25 @@
     @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
     fun testExtractsPromotedContent_whenBothFlagsEnabled() {
         val content = PromotedNotificationContentModel.Builder("key").build()
-        whenever(promotedNotificationContentExtractor.extractContent(any(), any()))
-            .thenReturn(content)
+        promotedNotificationContentExtractor.resetForEntry(row.entry, content)
 
         inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
 
-        verify(promotedNotificationContentExtractor, times(1)).extractContent(any(), any())
+        promotedNotificationContentExtractor.verifyOneExtractCall()
         Assert.assertEquals(content, row.entry.promotedNotificationContentModel)
     }
 
+    @Test
+    @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+    fun testExtractsPromotedContent_null() {
+        promotedNotificationContentExtractor.resetForEntry(row.entry, null)
+
+        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
+
+        promotedNotificationContentExtractor.verifyOneExtractCall()
+        Assert.assertNull(row.entry.promotedNotificationContentModel)
+    }
+
     private class ExceptionHolder {
         var exception: Exception? = null
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index b323ef8..b8d1875 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -81,7 +81,7 @@
 import com.android.systemui.statusbar.notification.icon.IconBuilder;
 import com.android.systemui.statusbar.notification.icon.IconManager;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
+import com.android.systemui.statusbar.notification.promoted.FakePromotedNotificationContentExtractor;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -201,7 +201,7 @@
                                 new MockSmartReplyInflater(),
                                 mock(NotifLayoutInflaterFactory.Provider.class),
                                 mock(HeadsUpStyleProvider.class),
-                                mock(PromotedNotificationContentExtractor.class),
+                                new FakePromotedNotificationContentExtractor(),
                                 mock(NotificationRowContentBinderLogger.class))
                         : new NotificationContentInflater(
                                 mock(NotifRemoteViewCache.class),
@@ -212,7 +212,7 @@
                                 new MockSmartReplyInflater(),
                                 mock(NotifLayoutInflaterFactory.Provider.class),
                                 mock(HeadsUpStyleProvider.class),
-                                mock(PromotedNotificationContentExtractor.class),
+                                new FakePromotedNotificationContentExtractor(),
                                 mock(NotificationRowContentBinderLogger.class));
         contentBinder.setInflateSynchronously(true);
         mBindStage = new RowContentBindStage(contentBinder,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index de40abb..c6cffa9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -952,11 +952,10 @@
     @Test
     @EnableSceneContainer
     public void onTouchEvent_stopExpandingNotification_sceneContainerEnabled() {
-        boolean touchHandled = stopExpandingNotification();
+        stopExpandingNotification();
 
-        verify(mNotificationStackScrollLayout).startOverscrollAfterExpanding();
+        verify(mExpandHelper).finishExpanding();
         verify(mNotificationStackScrollLayout, never()).dispatchDownEventToScroller(any());
-        assertTrue(touchHandled);
     }
 
     @Test
@@ -964,11 +963,11 @@
     public void onTouchEvent_stopExpandingNotification_sceneContainerDisabled() {
         stopExpandingNotification();
 
-        verify(mNotificationStackScrollLayout, never()).startOverscrollAfterExpanding();
+        verify(mExpandHelper, never()).finishExpanding();
         verify(mNotificationStackScrollLayout).dispatchDownEventToScroller(any());
     }
 
-    private boolean stopExpandingNotification() {
+    private void stopExpandingNotification() {
         when(mNotificationStackScrollLayout.getExpandHelper()).thenReturn(mExpandHelper);
         when(mNotificationStackScrollLayout.getIsExpanded()).thenReturn(true);
         when(mNotificationStackScrollLayout.getExpandedInThisMotion()).thenReturn(true);
@@ -983,13 +982,13 @@
         NotificationStackScrollLayoutController.TouchHandler touchHandler =
                 mController.getTouchHandler();
 
-        return touchHandler.onTouchEvent(MotionEvent.obtain(
-                /* downTime= */ 0,
-                /* eventTime= */ 0,
-                MotionEvent.ACTION_DOWN,
-                0,
-                0,
-                /* metaState= */ 0
+        touchHandler.onTouchEvent(MotionEvent.obtain(
+            /* downTime= */ 0,
+            /* eventTime= */ 0,
+            MotionEvent.ACTION_DOWN,
+            0,
+            0,
+            /* metaState= */ 0
         ));
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
index 06b3b57..b2378d2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.runOnMainThreadAndWaitForIdleSync
+import com.android.systemui.shade.data.repository.shadeDialogContextInteractor
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.systemUIDialogFactory
 import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel
@@ -78,6 +79,7 @@
                 { kosmos.modesDialogViewModel },
                 mockDialogEventLogger,
                 kosmos.mainCoroutineContext,
+                kosmos.shadeDialogContextInteractor,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 7b52dd8..5cd0846 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -501,6 +501,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
     public void onWallpaperColorsChanged_changeLockWallpaper() {
         // Should ask for a new theme when wallpaper colors change
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
diff --git a/packages/SystemUI/res/color/slider_active_track_color.xml b/packages/SystemUI/res/color/slider_active_track_color.xml
deleted file mode 100644
index 8ba5e49..0000000
--- a/packages/SystemUI/res/color/slider_active_track_color.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?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.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-    <item android:color="@androidprv:color/materialColorPrimary" android:state_enabled="true" />
-    <item android:color="@androidprv:color/materialColorSurfaceContainerHighest" />
-</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/slider_inactive_track_color.xml b/packages/SystemUI/res/color/slider_inactive_track_color.xml
deleted file mode 100644
index 7980f80..0000000
--- a/packages/SystemUI/res/color/slider_inactive_track_color.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?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.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-    <item android:color="@androidprv:color/materialColorSurfaceContainerHighest" android:state_enabled="true" />
-    <item android:color="@androidprv:color/materialColorPrimary" />
-</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/slider_thumb_color.xml b/packages/SystemUI/res/color/slider_thumb_color.xml
deleted file mode 100644
index 8a98902..0000000
--- a/packages/SystemUI/res/color/slider_thumb_color.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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.
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-    <item android:color="@androidprv:color/materialColorSurfaceContainerHighest" android:state_enabled="false" />
-    <item android:color="@androidprv:color/materialColorPrimary" />
-</selector>
diff --git a/packages/SystemUI/res/drawable/audio_bars_idle.xml b/packages/SystemUI/res/drawable/audio_bars_idle.xml
new file mode 100644
index 0000000..92a2475
--- /dev/null
+++ b/packages/SystemUI/res/drawable/audio_bars_idle.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2021 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="168dp"
+    android:height="168dp"
+    android:viewportWidth="168"
+    android:viewportHeight="168">
+    <group android:name="_R_G">
+        <group
+            android:name="_R_G_L_2_G"
+            android:translateX="121.161"
+            android:translateY="83.911">
+            <path
+                android:name="_R_G_L_2_G_D_0_P_0"
+                android:fillAlpha="1"
+                android:fillColor="#ffffff"
+                android:fillType="nonZero"
+                android:pathData=" M-37.16 -5.87 C-33.94,-5.87 -31.32,-3.32 -31.2,-0.13 C-31.2,-0.06 -31.2,0.02 -31.2,0.09 C-31.2,0.16 -31.2,0.23 -31.2,0.29 C-31.31,3.49 -33.94,6.05 -37.16,6.05 C-40.39,6.05 -43.01,3.49 -43.12,0.29 C-43.12,0.23 -43.12,0.16 -43.12,0.09 C-43.12,0.01 -43.12,-0.07 -43.12,-0.15 C-42.99,-3.33 -40.37,-5.87 -37.16,-5.87c " />
+        </group>
+        <group
+            android:name="_R_G_L_1_G"
+            android:translateX="102.911"
+            android:translateY="83.911">
+            <path
+                android:name="_R_G_L_1_G_D_0_P_0"
+                android:fillAlpha="1"
+                android:fillColor="#ffffff"
+                android:fillType="nonZero"
+                android:pathData=" M-37.16 -5.87 C-33.94,-5.87 -31.32,-3.32 -31.2,-0.13 C-31.2,-0.06 -31.2,0.02 -31.2,0.09 C-31.2,0.16 -31.2,0.23 -31.2,0.29 C-31.31,3.49 -33.94,6.05 -37.16,6.05 C-40.39,6.05 -43.01,3.49 -43.12,0.29 C-43.12,0.23 -43.12,0.16 -43.12,0.09 C-43.12,0.01 -43.12,-0.07 -43.12,-0.15 C-42.99,-3.33 -40.37,-5.87 -37.16,-5.87c " />
+        </group>
+        <group
+            android:name="_R_G_L_0_G"
+            android:translateX="139.661"
+            android:translateY="83.911">
+            <path
+                android:name="_R_G_L_0_G_D_0_P_0"
+                android:fillAlpha="1"
+                android:fillColor="#ffffff"
+                android:fillType="nonZero"
+                android:pathData=" M-37.16 -5.87 C-33.94,-5.87 -31.32,-3.32 -31.2,-0.13 C-31.2,-0.06 -31.2,0.02 -31.2,0.09 C-31.2,0.16 -31.2,0.23 -31.2,0.29 C-31.31,3.49 -33.94,6.05 -37.16,6.05 C-40.39,6.05 -43.01,3.49 -43.12,0.29 C-43.12,0.23 -43.12,0.16 -43.12,0.09 C-43.12,0.01 -43.12,-0.07 -43.12,-0.15 C-42.99,-3.33 -40.37,-5.87 -37.16,-5.87c " />
+        </group>
+    </group>
+    <group android:name="time_group" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index a3bad8f..5ccedea 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -56,8 +56,8 @@
         android:layout_marginTop="@dimen/volume_dialog_components_spacing"
         android:background="@drawable/ripple_drawable_20dp"
         android:contentDescription="@string/accessibility_volume_settings"
+        android:scaleType="centerInside"
         android:soundEffectsEnabled="false"
-        android:src="@drawable/horizontal_ellipsis"
         android:tint="@androidprv:color/materialColorPrimary"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container"
diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml
index 9ac456c..0acf410 100644
--- a/packages/SystemUI/res/layout/volume_dialog_slider.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml
@@ -14,7 +14,6 @@
      limitations under the License.
 -->
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content">
 
@@ -24,11 +23,6 @@
         android:layout_width="@dimen/volume_dialog_slider_width"
         android:layout_height="@dimen/volume_dialog_slider_height"
         android:layout_gravity="center"
-        android:theme="@style/Theme.Material3.Light"
         android:orientation="vertical"
-        app:thumbHeight="52dp"
-        app:trackCornerSize="12dp"
-        app:trackHeight="40dp"
-        app:trackStopIndicatorSize="6dp"
-        app:trackInsideCornerSize="2dp" />
+        android:theme="@style/Theme.Material3.DayNight" />
 </FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/audio_bars_in.json b/packages/SystemUI/res/raw/audio_bars_in.json
new file mode 100644
index 0000000..c90a59c
--- /dev/null
+++ b/packages/SystemUI/res/raw/audio_bars_in.json
@@ -0,0 +1 @@
+{"v":"5.7.13","fr":60,"ip":0,"op":18,"w":168,"h":168,"nm":"audio_bars_in","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[120.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[102.5,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[65.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[84,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.25,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/audio_bars_out.json b/packages/SystemUI/res/raw/audio_bars_out.json
new file mode 100644
index 0000000..5eab65e0
--- /dev/null
+++ b/packages/SystemUI/res/raw/audio_bars_out.json
@@ -0,0 +1 @@
+{"v":"5.7.13","fr":60,"ip":0,"op":31,"w":168,"h":168,"nm":"audio_bars_out","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[120.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[102.5,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[65.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[84,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.25,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/audio_bars_playing.json b/packages/SystemUI/res/raw/audio_bars_playing.json
new file mode 100644
index 0000000..6ee8e19
--- /dev/null
+++ b/packages/SystemUI/res/raw/audio_bars_playing.json
@@ -0,0 +1 @@
+{"v":"5.7.13","fr":60,"ip":0,"op":121,"w":168,"h":168,"nm":"audio_bars_playing","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[120.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":38,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[-0.016,-14.1],[5.941,-8.358],[5.961,0],[5.958,8.516],[0,14.274],[-5.958,8.515],[-5.961,0],[-5.972,-8.373]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":70,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":102,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[-0.016,-14.1],[5.941,-8.358],[5.961,0],[5.958,8.516],[0,14.274],[-5.958,8.515],[-5.961,0],[-5.972,-8.373]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[102.5,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":32,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-19.1],[5.957,-13.358],[5.961,0],[5.958,13.641],[0,19.399],[-5.958,13.64],[-5.961,0],[-5.957,-13.373]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":65,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":97,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-19.1],[5.957,-13.358],[5.961,0],[5.958,13.641],[0,19.399],[-5.958,13.64],[-5.961,0],[-5.957,-13.373]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[65.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":29,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-15.85],[5.957,-10.108],[5.961,0],[5.958,10.516],[0,16.274],[-5.958,10.515],[-5.961,0],[-5.957,-10.123]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":59,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":91,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-15.85],[5.957,-10.108],[5.961,0],[5.958,10.516],[0,16.274],[-5.958,10.515],[-5.961,0],[-5.957,-10.123]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[84,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":24,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-9.225],[5.957,-3.483],[5.961,0],[5.958,3.766],[0,9.524],[-5.958,3.765],[-5.961,0],[-5.957,-3.498]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":54,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":86,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-9.225],[5.957,-3.483],[5.961,0],[5.958,3.766],[0,9.524],[-5.958,3.765],[-5.961,0],[-5.957,-3.498]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.25,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":19,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":48,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":81,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0}],"markers":[{"tm":60,"cm":"1","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index c1eff5f..a77f5e4 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -108,6 +108,9 @@
 
     <color name="people_tile_background">@color/material_dynamic_secondary20</color>
 
+    <!-- Dark Theme colors for notification shade/scrim -->
+    <color name="shade_panel">@android:color/system_accent1_900</color>
+
     <!-- Keyboard shortcut helper dialog -->
     <color name="ksh_key_item_color">@*android:color/system_on_surface_variant_dark</color>
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index b438315..ab0f788 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -19,7 +19,7 @@
 
 <!-- These resources are around just to allow their values to be customized
      for different hardware and product builds. -->
-<resources>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
     <!-- The maximum number of rows in the QuickSettings -->
     <integer name="quick_settings_max_rows">4</integer>
 
@@ -51,7 +51,9 @@
     ignored. -->
     <string-array name="config_keyguardQuickAffordanceDefaults" translatable="false">
         <item>bottom_start:home</item>
-        <item>bottom_end:create_note</item>
+        <!-- TODO(b/384119565): revisit decision on defaults -->
+        <item android:featureFlag="!com.android.systemui.glanceable_hub_v2_resources">bottom_end:create_note</item>
+        <item android:featureFlag="com.android.systemui.glanceable_hub_v2_resources">bottom_end:glanceable_hub</item>
     </string-array>
 
     <!-- Whether volume panel should use the large screen layout or not -->
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 28df2e2..d2b7d0b 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -31,6 +31,9 @@
     <!-- The dark background color behind the shade -->
     <color name="shade_scrim_background_dark">@androidprv:color/system_under_surface_light</color>
 
+    <!-- Colors for notification shade/scrim -->
+    <color name="shade_panel">@android:color/system_accent1_800</color>
+
     <!-- The color of the background in the separated list of the Global Actions menu -->
     <color name="global_actions_separated_background">#F5F5F5</color>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 56aaf4c..d3ee63ba 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2504,14 +2504,14 @@
     <!-- Accessibility description of action to remove QS tile on click. It will read as "Double-tap to remove tile" in screen readers [CHAR LIMIT=NONE] -->
     <string name="accessibility_qs_edit_remove_tile_action">remove tile</string>
 
-    <!-- Accessibility action of action to add QS tile to end. It will read as "Double-tap to add tile to end" in screen readers [CHAR LIMIT=NONE] -->
-    <string name="accessibility_qs_edit_tile_add_action">add tile to end</string>
+    <!-- Accessibility action of action to add QS tile to end. It will read as "Double-tap to add tile to the last position" in screen readers [CHAR LIMIT=NONE] -->
+    <string name="accessibility_qs_edit_tile_add_action">add tile to the last position</string>
 
     <!-- Accessibility action for context menu to move QS tile [CHAR LIMIT=NONE] -->
     <string name="accessibility_qs_edit_tile_start_move">Move tile</string>
 
-    <!-- Accessibility action for context menu to add QS tile [CHAR LIMIT=NONE] -->
-    <string name="accessibility_qs_edit_tile_start_add">Add tile</string>
+    <!-- Accessibility action for context menu to add QS tile to a position [CHAR LIMIT=NONE] -->
+    <string name="accessibility_qs_edit_tile_start_add">Add tile to desired position</string>
 
     <!-- Accessibility description when QS tile is to be moved, indicating the destination position [CHAR LIMIT=NONE] -->
     <string name="accessibility_qs_edit_tile_move_to_position">Move to <xliff:g id="position" example="5">%1$d</xliff:g></string>
@@ -2564,7 +2564,7 @@
     <string name="accessibility_quick_settings_open_settings">Open <xliff:g name="page" example="Bluetooth">%s</xliff:g> settings.</string>
 
     <!-- accessibility label for button to edit quick settings [CHAR LIMIT=NONE] -->
-    <string name="accessibility_quick_settings_edit">Edit order of settings.</string>
+    <string name="accessibility_quick_settings_edit">Edit order of Quick Settings.</string>
 
     <!-- accessibility label for button to open power menu [CHAR LIMIT=NONE] -->
     <string name="accessibility_quick_settings_power_menu">Power menu</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 3156a50..f6c1ecea 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -559,15 +559,18 @@
     <style name="SystemUI.Material3.Slider.Volume">
         <item name="trackHeight">40dp</item>
         <item name="thumbHeight">52dp</item>
+        <item name="trackCornerSize">12dp</item>
+        <item name="trackInsideCornerSize">2dp</item>
+        <item name="trackStopIndicatorSize">6dp</item>
     </style>
 
     <style name="SystemUI.Material3.Slider" parent="@style/Widget.Material3.Slider">
         <item name="labelStyle">@style/Widget.Material3.Slider.Label</item>
-        <item name="thumbColor">@color/slider_thumb_color</item>
-        <item name="tickColorActive">@color/slider_inactive_track_color</item>
-        <item name="tickColorInactive">@color/slider_active_track_color</item>
-        <item name="trackColorActive">@color/slider_active_track_color</item>
-        <item name="trackColorInactive">@color/slider_inactive_track_color</item>
+        <item name="thumbColor">@androidprv:color/materialColorPrimary</item>
+        <item name="tickColorActive">@androidprv:color/materialColorSurfaceContainerHighest</item>
+        <item name="tickColorInactive">@androidprv:color/materialColorPrimary</item>
+        <item name="trackColorActive">@androidprv:color/materialColorPrimary</item>
+        <item name="trackColorInactive">@androidprv:color/materialColorSurfaceContainerHighest</item>
     </style>
 
     <style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"/>
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 5af80cb..71b622a 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -43,7 +43,6 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags.REGION_SAMPLING
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.Edge
@@ -85,8 +84,8 @@
 import kotlinx.coroutines.flow.merge
 
 /**
- * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
- * [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController].
+ * Controller for a Clock provided by the registry and used on the keyguard. Functionality is forked
+ * from [AnimatableClockController].
  */
 open class ClockEventController
 @Inject
@@ -348,14 +347,6 @@
         object : KeyguardUpdateMonitorCallback() {
             override fun onKeyguardVisibilityChanged(visible: Boolean) {
                 isKeyguardVisible = visible
-                if (!MigrateClocksToBlueprint.isEnabled) {
-                    if (!isKeyguardVisible) {
-                        clock?.run {
-                            smallClock.animations.doze(if (isDozing) 1f else 0f)
-                            largeClock.animations.doze(if (isDozing) 1f else 0f)
-                        }
-                    }
-                }
 
                 if (visible) {
                     refreshTime()
@@ -388,10 +379,6 @@
             }
 
             private fun refreshTime() {
-                if (!MigrateClocksToBlueprint.isEnabled) {
-                    return
-                }
-
                 clock?.smallClock?.events?.onTimeTick()
                 clock?.largeClock?.events?.onTimeTick()
             }
@@ -483,14 +470,10 @@
                     if (ModesUi.isEnabled) {
                         listenForDnd(this)
                     }
-                    if (MigrateClocksToBlueprint.isEnabled) {
-                        listenForDozeAmountTransition(this)
-                        listenForAnyStateToAodTransition(this)
-                        listenForAnyStateToLockscreenTransition(this)
-                        listenForAnyStateToDozingTransition(this)
-                    } else {
-                        listenForDozeAmount(this)
-                    }
+                    listenForDozeAmountTransition(this)
+                    listenForAnyStateToAodTransition(this)
+                    listenForAnyStateToLockscreenTransition(this)
+                    listenForAnyStateToDozingTransition(this)
                 }
             }
         smallTimeListener?.update(shouldTimeListenerRun)
@@ -596,11 +579,6 @@
     }
 
     @VisibleForTesting
-    internal fun listenForDozeAmount(scope: CoroutineScope): Job {
-        return scope.launch { keyguardInteractor.dozeAmount.collect { handleDoze(it) } }
-    }
-
-    @VisibleForTesting
     internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
         return scope.launch {
             merge(
@@ -695,8 +673,7 @@
             isRunning = true
             when (clockFace.config.tickRate) {
                 ClockTickRate.PER_MINUTE -> {
-                    // Handled by KeyguardClockSwitchController and
-                    // by KeyguardUpdateMonitorCallback#onTimeChanged.
+                    // Handled by KeyguardUpdateMonitorCallback#onTimeChanged.
                 }
                 ClockTickRate.PER_SECOND -> executor.execute(secondsRunnable)
                 ClockTickRate.PER_FRAME -> {
diff --git a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
index df77a58..3f332f7 100644
--- a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
@@ -23,14 +23,11 @@
 import android.os.Bundle
 import android.view.Display
 import android.view.Gravity
-import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.view.WindowManager
 import android.widget.FrameLayout
 import android.widget.FrameLayout.LayoutParams
-import com.android.keyguard.dagger.KeyguardStatusViewComponent
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockFaceController
 import com.android.systemui.res.R
@@ -45,7 +42,6 @@
 constructor(
     @Assisted display: Display,
     context: Context,
-    private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory,
     private val clockRegistry: ClockRegistry,
     private val clockEventController: ClockEventController,
 ) :
@@ -53,12 +49,11 @@
         context,
         display,
         R.style.Theme_SystemUI_KeyguardPresentation,
-        WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
+        WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
     ) {
 
     private lateinit var rootView: FrameLayout
     private var clock: View? = null
-    private lateinit var keyguardStatusViewController: KeyguardStatusViewController
     private lateinit var faceController: ClockFaceController
     private lateinit var clockFrame: FrameLayout
 
@@ -82,7 +77,7 @@
                 oldLeft: Int,
                 oldTop: Int,
                 oldRight: Int,
-                oldBottom: Int
+                oldBottom: Int,
             ) {
                 clock?.let {
                     faceController.events.onTargetRegionChanged(
@@ -95,11 +90,7 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
-        if (MigrateClocksToBlueprint.isEnabled) {
-            onCreateV2()
-        } else {
-            onCreate()
-        }
+        onCreateV2()
     }
 
     fun onCreateV2() {
@@ -112,39 +103,15 @@
         setClock(clockRegistry.createCurrentClock())
     }
 
-    fun onCreate() {
-        setContentView(
-            LayoutInflater.from(context)
-                .inflate(R.layout.keyguard_clock_presentation, /* root= */ null)
-        )
-
-        setFullscreen()
-
-        clock = requireViewById(R.id.clock)
-        keyguardStatusViewController =
-            keyguardStatusViewComponentFactory
-                .build(clock as KeyguardStatusView, display)
-                .keyguardStatusViewController
-                .apply {
-                    setDisplayedOnSecondaryDisplay()
-                    init()
-                }
-    }
-
     override fun onAttachedToWindow() {
-        if (MigrateClocksToBlueprint.isEnabled) {
-            clockRegistry.registerClockChangeListener(clockChangedListener)
-            clockEventController.registerListeners(clock!!)
-
-            faceController.animations.enter()
-        }
+        clockRegistry.registerClockChangeListener(clockChangedListener)
+        clockEventController.registerListeners(clock!!)
+        faceController.animations.enter()
     }
 
     override fun onDetachedFromWindow() {
-        if (MigrateClocksToBlueprint.isEnabled) {
-            clockEventController.unregisterListeners()
-            clockRegistry.unregisterClockChangeListener(clockChangedListener)
-        }
+        clockEventController.unregisterListeners()
+        clockRegistry.unregisterClockChangeListener(clockChangedListener)
 
         super.onDetachedFromWindow()
     }
@@ -166,7 +133,7 @@
                 context.resources.getDimensionPixelSize(R.dimen.keyguard_presentation_width),
                 WRAP_CONTENT,
                 Gravity.CENTER,
-            )
+            ),
         )
 
         clockEventController.clock = clockController
@@ -190,8 +157,6 @@
     @AssistedFactory
     interface Factory {
         /** Creates a new [Presentation] for the given [display]. */
-        fun create(
-            display: Display,
-        ): ConnectedDisplayKeyguardPresentation
+        fun create(display: Display): ConnectedDisplayKeyguardPresentation
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 0305b5e..e76f38c 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -26,7 +26,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.keyguard.MigrateClocksToBlueprint;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.clocks.ClockMessageBuffers;
 import com.android.systemui.res.R;
@@ -70,7 +69,7 @@
                         context,
                         layoutInflater,
                         resources,
-                        MigrateClocksToBlueprint.isEnabled(),
+
                         com.android.systemui.Flags.clockReactiveVariants()
                 ),
                 context.getString(R.string.lockscreen_clock_id_fallback),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 4c2dc41..d8c628f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -155,6 +155,7 @@
                     override fun onAnimationEnd(animation: Animator) {
                         drawDwell = false
                         resetDwellAlpha()
+                        invalidate()
                     }
                 })
                 start()
@@ -191,6 +192,7 @@
                     override fun onAnimationEnd(animation: Animator) {
                         drawDwell = false
                         resetDwellAlpha()
+                        invalidate()
                     }
                 })
                 start()
@@ -248,6 +250,7 @@
 
                 override fun onAnimationEnd(animation: Animator) {
                     drawDwell = false
+                    invalidate()
                 }
             })
             start()
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index 9cfb5be..b294dd1 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -41,6 +41,7 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.util.time.SystemClock
 import dagger.assisted.Assisted
@@ -68,6 +69,7 @@
     private val uiEventLogger: UiEventLogger,
     private val logger: BluetoothTileDialogLogger,
     private val systemuiDialogFactory: SystemUIDialog.Factory,
+    private val shadeDialogContextInteractor: ShadeDialogContextInteractor,
 ) : SystemUIDialog.Delegate {
 
     private val mutableBluetoothStateToggle: MutableStateFlow<Boolean?> = MutableStateFlow(null)
@@ -105,7 +107,7 @@
     }
 
     override fun createDialog(): SystemUIDialog {
-        return systemuiDialogFactory.create(this)
+        return systemuiDialogFactory.create(this, shadeDialogContextInteractor.context)
     }
 
     override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
@@ -405,10 +407,11 @@
                     }
 
                     // updating icon colors
-                    val tintColor = context.getColor(
-                        if (item.isActive) InternalR.color.materialColorOnPrimaryContainer
-                        else InternalR.color.materialColorOnSurface
-                    )
+                    val tintColor =
+                        context.getColor(
+                            if (item.isActive) InternalR.color.materialColorOnPrimaryContainer
+                            else InternalR.color.materialColorOnSurface
+                        )
 
                     // update icons
                     iconView.apply {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index 26abb48..73c0179 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -55,6 +55,8 @@
     /** A [CommunalEnabledState] for the specified user. */
     fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState>
 
+    fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean>
+
     /**
      * Returns true if any glanceable hub functionality should be enabled via configs and flags.
      *
@@ -138,6 +140,20 @@
             .flowOn(bgDispatcher)
     }
 
+    override fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean> =
+        secureSettings
+            .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.SCREENSAVER_ENABLED))
+            // Force an update
+            .onStart { emit(Unit) }
+            .map {
+                secureSettings.getIntForUser(
+                    Settings.Secure.SCREENSAVER_ENABLED,
+                    SCREENSAVER_ENABLED_SETTING_DEFAULT,
+                    user.id,
+                ) == 1
+            }
+            .flowOn(bgDispatcher)
+
     override fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> =
         broadcastDispatcher
             .broadcastFlow(
@@ -182,6 +198,7 @@
     companion object {
         const val GLANCEABLE_HUB_BACKGROUND_SETTING = "glanceable_hub_background"
         private const val ENABLED_SETTING_DEFAULT = 1
+        private const val SCREENSAVER_ENABLED_SETTING_DEFAULT = 0
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
index 862b05b..c1f21e40 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
@@ -69,6 +69,12 @@
             // Start this eagerly since the value is accessed synchronously in many places.
             .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
 
+    /** Whether or not screensaver (dreams) is enabled for the currently selected user. */
+    val isScreensaverEnabled: Flow<Boolean> =
+        userInteractor.selectedUserInfo.flatMapLatest { user ->
+            repository.getScreensaverEnabledState(user)
+        }
+
     /**
      * Returns true if any glanceable hub functionality should be enabled via configs and flags.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt
index 7d5b196..c6f96e1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt
@@ -18,10 +18,15 @@
 
 import android.annotation.SuppressLint
 import android.app.DreamManager
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.util.kotlin.isDevicePluggedIn
+import com.android.systemui.util.kotlin.sample
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlin.coroutines.CoroutineContext
@@ -31,7 +36,6 @@
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.receiveAsFlow
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
@@ -41,6 +45,8 @@
 constructor(
     @Background private val backgroundContext: CoroutineContext,
     batteryController: BatteryController,
+    private val settingsInteractor: CommunalSettingsInteractor,
+    private val activityStarter: ActivityStarter,
     private val dreamManager: DreamManager,
 ) : ExclusiveActivatable() {
 
@@ -49,11 +55,7 @@
     /** Whether we should show a button on hub to switch to dream. */
     @SuppressLint("MissingPermission")
     val shouldShowDreamButtonOnHub =
-        batteryController
-            .isDevicePluggedIn()
-            .distinctUntilChanged()
-            .map { isPluggedIn -> isPluggedIn && dreamManager.canStartDreaming(true) }
-            .flowOn(backgroundContext)
+        batteryController.isDevicePluggedIn().distinctUntilChanged().flowOn(backgroundContext)
 
     /** Handle a tap on the "show dream" button. */
     fun onShowDreamButtonTap() {
@@ -63,9 +65,21 @@
     @SuppressLint("MissingPermission")
     override suspend fun onActivated(): Nothing = coroutineScope {
         launch {
-            _requests.receiveAsFlow().collectLatest {
-                withContext(backgroundContext) { dreamManager.startDream() }
-            }
+            _requests
+                .receiveAsFlow()
+                .sample(settingsInteractor.isScreensaverEnabled)
+                .collectLatest { enabled ->
+                    withContext(backgroundContext) {
+                        if (enabled) {
+                            dreamManager.startDream()
+                        } else {
+                            activityStarter.postStartActivityDismissingKeyguard(
+                                Intent(Settings.ACTION_DREAM_SETTINGS),
+                                0,
+                            )
+                        }
+                    }
+                }
         }
 
         awaitCancellation()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 160380b..57fe15d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -24,7 +24,6 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
@@ -50,9 +49,6 @@
     }
 
     override fun addViews(constraintLayout: ConstraintLayout) {
-        if (!MigrateClocksToBlueprint.isEnabled) {
-            return
-        }
         if (emptyView.parent != null) {
             // As emptyView is lazy, it might be already attached.
             (emptyView.parent as? ViewGroup)?.removeView(emptyView)
@@ -68,17 +64,10 @@
     }
 
     override fun bindData(constraintLayout: ConstraintLayout) {
-        if (!MigrateClocksToBlueprint.isEnabled) {
-            return
-        }
         clockViewModel.burnInLayer = burnInLayer
     }
 
     override fun applyConstraints(constraintSet: ConstraintSet) {
-        if (!MigrateClocksToBlueprint.isEnabled) {
-            return
-        }
-
         constraintSet.apply {
             // The empty view should not occupy any space
             constrainHeight(R.id.burn_in_layer_empty_view, 1)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index 70bf8bc..738fb73 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -32,7 +32,6 @@
 import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
 import com.android.systemui.customization.R as customR
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardSection
@@ -82,9 +81,6 @@
     override fun addViews(constraintLayout: ConstraintLayout) {}
 
     override fun bindData(constraintLayout: ConstraintLayout) {
-        if (!MigrateClocksToBlueprint.isEnabled) {
-            return
-        }
         disposableHandle?.dispose()
         disposableHandle =
             KeyguardClockViewBinder.bind(
@@ -99,20 +95,12 @@
     }
 
     override fun applyConstraints(constraintSet: ConstraintSet) {
-        if (!MigrateClocksToBlueprint.isEnabled) {
-            return
-        }
-
         keyguardClockViewModel.currentClock.value?.let { clock ->
             constraintSet.applyDeltaFrom(buildConstraints(clock, constraintSet))
         }
     }
 
     override fun removeViews(constraintLayout: ConstraintLayout) {
-        if (!MigrateClocksToBlueprint.isEnabled) {
-            return
-        }
-
         disposableHandle?.dispose()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index 3a791fd..4bfe5f0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -24,7 +24,6 @@
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.res.R
 import com.android.systemui.shade.LargeScreenHeaderHelper
 import com.android.systemui.shade.NotificationPanelView
@@ -54,32 +53,25 @@
         sharedNotificationContainerBinder,
     ) {
     override fun applyConstraints(constraintSet: ConstraintSet) {
-        if (!MigrateClocksToBlueprint.isEnabled) {
-            return
-        }
         constraintSet.apply {
             val bottomMargin =
                 context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin)
-            if (MigrateClocksToBlueprint.isEnabled) {
-                val useLargeScreenHeader =
-                    context.resources.getBoolean(R.bool.config_use_large_screen_shade_header)
-                val marginTopLargeScreen =
-                    largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
-                connect(
-                    R.id.nssl_placeholder,
-                    TOP,
-                    R.id.smart_space_barrier_bottom,
-                    BOTTOM,
-                    bottomMargin +
-                        if (useLargeScreenHeader) {
-                            marginTopLargeScreen
-                        } else {
-                            0
-                        }
-                )
-            } else {
-                connect(R.id.nssl_placeholder, TOP, R.id.keyguard_status_view, BOTTOM, bottomMargin)
-            }
+            val useLargeScreenHeader =
+                context.resources.getBoolean(R.bool.config_use_large_screen_shade_header)
+            val marginTopLargeScreen =
+                largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
+            connect(
+                R.id.nssl_placeholder,
+                TOP,
+                R.id.smart_space_barrier_bottom,
+                BOTTOM,
+                bottomMargin +
+                    if (useLargeScreenHeader) {
+                        marginTopLargeScreen
+                    } else {
+                        0
+                    },
+            )
             connect(R.id.nssl_placeholder, START, PARENT_ID, START)
             connect(R.id.nssl_placeholder, END, PARENT_ID, END)
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
index 620cc13..fc26d18 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
@@ -25,7 +25,6 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
 import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
@@ -62,9 +61,6 @@
     }
 
     override fun addViews(constraintLayout: ConstraintLayout) {
-        if (!MigrateClocksToBlueprint.isEnabled) {
-            return
-        }
         // This moves the existing NSSL view to a different parent, as the controller is a
         // singleton and recreating it has other bad side effects.
         // In the SceneContainer, this is done by the NotificationSection composable.
@@ -78,10 +74,6 @@
     }
 
     override fun bindData(constraintLayout: ConstraintLayout) {
-        if (!MigrateClocksToBlueprint.isEnabled) {
-            return
-        }
-
         disposableHandle?.dispose()
         disposableHandle =
             sharedNotificationContainerBinder.bind(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index 73e14b1..cd038d7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.customization.R as customR
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardSection
@@ -70,7 +69,6 @@
     }
 
     override fun addViews(constraintLayout: ConstraintLayout) {
-        if (!MigrateClocksToBlueprint.isEnabled) return
         if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
         smartspaceView = smartspaceController.buildAndConnectView(constraintLayout)
         weatherView = smartspaceController.buildAndConnectWeatherView(constraintLayout)
@@ -98,7 +96,6 @@
     }
 
     override fun bindData(constraintLayout: ConstraintLayout) {
-        if (!MigrateClocksToBlueprint.isEnabled) return
         if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
         disposableHandle?.dispose()
         disposableHandle =
@@ -111,13 +108,11 @@
     }
 
     override fun applyConstraints(constraintSet: ConstraintSet) {
-        if (!MigrateClocksToBlueprint.isEnabled) return
         if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
         val dateWeatherPaddingStart = KeyguardSmartspaceViewModel.getDateWeatherStartMargin(context)
         val smartspaceHorizontalPadding =
             KeyguardSmartspaceViewModel.getSmartspaceHorizontalMargin(context)
         constraintSet.apply {
-            // migrate addDateWeatherView, addWeatherView from KeyguardClockSwitchController
             constrainHeight(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
             constrainWidth(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
             connect(
@@ -128,7 +123,6 @@
                 dateWeatherPaddingStart,
             )
 
-            // migrate addSmartspaceView from KeyguardClockSwitchController
             constrainHeight(sharedR.id.bc_smartspace_view, ConstraintSet.WRAP_CONTENT)
             constrainWidth(sharedR.id.bc_smartspace_view, ConstraintSet.MATCH_CONSTRAINT)
             connect(
@@ -182,7 +176,6 @@
     }
 
     override fun removeViews(constraintLayout: ConstraintLayout) {
-        if (!MigrateClocksToBlueprint.isEnabled) return
         if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
         listOf(smartspaceView, dateWeatherView).forEach {
             it?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/lottie/LottieTaskExt.kt b/packages/SystemUI/src/com/android/systemui/lottie/LottieTaskExt.kt
new file mode 100644
index 0000000..dd2525f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lottie/LottieTaskExt.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.lottie
+
+import com.airbnb.lottie.LottieComposition
+import com.airbnb.lottie.LottieListener
+import com.airbnb.lottie.LottieTask
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+/**
+ * Suspends until [LottieTask] is finished with a result or a failure.
+ *
+ * @return result of the [LottieTask] when it's successful
+ */
+suspend fun LottieTask<LottieComposition>.await() =
+    suspendCancellableCoroutine<LottieComposition> { continuation ->
+        val resultListener =
+            LottieListener<LottieComposition> { result ->
+                with(continuation) { if (!isCancelled && !isCompleted) resume(result) }
+            }
+        val failureListener =
+            LottieListener<Throwable> { throwable ->
+                with(continuation) {
+                    if (!isCancelled && !isCompleted) resumeWithException(throwable)
+                }
+            }
+        addListener(resultListener)
+        addFailureListener(failureListener)
+        continuation.invokeOnCancellation {
+            removeListener(resultListener)
+            removeFailureListener(failureListener)
+        }
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 574ccee..ab998d1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -366,7 +366,6 @@
                                     / (double) seekBar.getMax());
                     mVolumeValueText.setText(mContext.getResources().getString(
                             R.string.media_output_dialog_volume_percentage, percentage));
-                    mVolumeValueText.setVisibility(View.VISIBLE);
                     if (mStartFromMute) {
                         updateUnmutedVolumeIcon(device);
                         mStartFromMute = false;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 91a3120..1e608af1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -67,6 +67,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
 import com.android.systemui.shared.system.SysUiStatsLog
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.util.DeviceConfigProxy
@@ -141,7 +142,6 @@
 class FgsManagerControllerImpl
 @Inject
 constructor(
-    @ShadeDisplayAware private val context: Context,
     @ShadeDisplayAware private val resources: Resources,
     @Main private val mainExecutor: Executor,
     @Background private val backgroundExecutor: Executor,
@@ -155,6 +155,7 @@
     private val broadcastDispatcher: BroadcastDispatcher,
     private val dumpManager: DumpManager,
     private val systemUIDialogFactory: SystemUIDialog.Factory,
+    private val shadeDialogContextRepository: ShadeDialogContextInteractor,
 ) : Dumpable, FgsManagerController {
 
     companion object {
@@ -388,7 +389,7 @@
     override fun showDialog(expandable: Expandable?) {
         synchronized(lock) {
             if (dialog == null) {
-                val dialog = systemUIDialogFactory.create(context)
+                val dialog = systemUIDialogFactory.create(shadeDialogContextRepository.context)
                 dialog.setTitle(R.string.fgs_manager_dialog_title)
                 dialog.setMessage(R.string.fgs_manager_dialog_message)
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
index 790793e..3049a40 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
@@ -17,16 +17,16 @@
 package com.android.systemui.qs.composefragment.ui
 
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithCache
+import androidx.compose.ui.draw.drawWithContent
 import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.BlendMode
 import androidx.compose.ui.graphics.ClipOp
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.CompositingStrategy
 import androidx.compose.ui.graphics.drawscope.clipRect
-import androidx.compose.ui.graphics.layer.CompositingStrategy
-import androidx.compose.ui.graphics.layer.drawLayer
+import androidx.compose.ui.graphics.graphicsLayer
 
 /**
  * Clipping modifier for clipping out the notification scrim as it slides over QS. It will clip out
@@ -34,16 +34,16 @@
  * from the QS container.
  */
 fun Modifier.notificationScrimClip(clipParams: () -> NotificationScrimClipParams): Modifier {
-    return this.drawWithCache {
+    return this.graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }
+        .drawWithContent {
+            drawContent()
             val params = clipParams()
             val left = -params.leftInset.toFloat()
             val right = size.width + params.rightInset.toFloat()
             val top = params.top.toFloat()
             val bottom = params.bottom.toFloat()
-            val graphicsLayer = obtainGraphicsLayer()
-            graphicsLayer.compositingStrategy = CompositingStrategy.Offscreen
-            graphicsLayer.record {
-                drawContent()
+            val clipSize = Size(right - left, bottom - top)
+            if (!clipSize.isEmpty()) {
                 clipRect {
                     drawRoundRect(
                         color = Color.Black,
@@ -54,9 +54,6 @@
                     )
                 }
             }
-            onDrawWithContent {
-                drawLayer(graphicsLayer)
-            }
         }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 873059e..e7fa271 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -675,17 +675,11 @@
         }
 
         private void add() {
-            if (addFromPosition(getLayoutPosition())) {
-                itemView.announceForAccessibility(
-                        itemView.getContext().getText(R.string.accessibility_qs_edit_tile_added));
-            }
+            addFromPosition(getLayoutPosition());
         }
 
         private void remove() {
-            if (removeFromPosition(getLayoutPosition())) {
-                itemView.announceForAccessibility(
-                        itemView.getContext().getText(R.string.accessibility_qs_edit_tile_removed));
-            }
+            removeFromPosition(getLayoutPosition());
         }
 
         boolean isCurrentTile() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt
index 85db952..f3c06a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt
@@ -54,7 +54,7 @@
         ) {
             Icon(
                 imageVector = Icons.Default.Edit,
-                contentDescription = stringResource(id = R.string.qs_edit),
+                contentDescription = stringResource(id = R.string.accessibility_quick_settings_edit),
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 42a0cb1..b7ff63c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -41,6 +41,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.res.R;
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.DataSaverController;
 
@@ -56,6 +57,7 @@
     private final DataSaverController mDataSaverController;
     private final DialogTransitionAnimator mDialogTransitionAnimator;
     private final SystemUIDialog.Factory mSystemUIDialogFactory;
+    private final ShadeDialogContextInteractor mShadeDialogContextInteractor;
 
     @Inject
     public DataSaverTile(
@@ -70,13 +72,15 @@
             QSLogger qsLogger,
             DataSaverController dataSaverController,
             DialogTransitionAnimator dialogTransitionAnimator,
-            SystemUIDialog.Factory systemUIDialogFactory
+            SystemUIDialog.Factory systemUIDialogFactory,
+            ShadeDialogContextInteractor shadeDialogContextInteractor
     ) {
         super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mDataSaverController = dataSaverController;
         mDialogTransitionAnimator = dialogTransitionAnimator;
         mSystemUIDialogFactory = systemUIDialogFactory;
+        mShadeDialogContextInteractor = shadeDialogContextInteractor;
         mDataSaverController.observe(getLifecycle(), this);
     }
 
@@ -102,7 +106,8 @@
         // Show a dialog to confirm first. Dialogs shown by the DialogTransitionAnimator must be
         // created and shown on the main thread, so we post it to the UI handler.
         mUiHandler.post(() -> {
-            SystemUIDialog dialog = mSystemUIDialogFactory.create(mContext);
+            SystemUIDialog dialog = mSystemUIDialogFactory.create(
+                    mShadeDialogContextInteractor.getContext());
             dialog.setTitle(com.android.internal.R.string.data_saver_enable_title);
             dialog.setMessage(com.android.internal.R.string.data_saver_description);
             dialog.setPositiveButton(com.android.internal.R.string.data_saver_enable_button,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
index 19b45d5..7516ca0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
@@ -193,7 +193,7 @@
                 if (mJob == null) {
                     mJob = WifiUtils.checkWepAllowed(mContext, mCoroutineScope, wifiEntry.getSsid(),
                             WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, intent -> {
-                                mInternetDialogController.startActivity(intent, view);
+                                mInternetDialogController.startActivityForDialog(intent);
                                 return null;
                             }, () -> {
                                 wifiConnect(wifiEntry, view);
@@ -211,7 +211,7 @@
                         true /* connectForCaller */);
                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                 intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
-                mContext.startActivity(intent);
+                mInternetDialogController.startActivityForDialog(intent);
                 return;
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index dbe1ae9..7036ef91 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -781,6 +781,10 @@
         mActivityStarter.postStartActivityDismissingKeyguard(intent, 0, controller);
     }
 
+    void startActivityForDialog(Intent intent) {
+        mActivityStarter.startActivity(intent, false /* dismissShade */);
+    }
+
     void launchNetworkSetting(View view) {
         startActivity(getSettingsIntent(), view);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index 70c2a2a..5e9deec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -72,6 +72,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.res.R;
 import com.android.systemui.shade.ShadeDisplayAware;
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.wifitrackerlib.WifiEntry;
@@ -104,9 +105,9 @@
     private final Handler mHandler;
     private final Executor mBackgroundExecutor;
     private final DialogTransitionAnimator mDialogTransitionAnimator;
-    private final Context mContext;
     private final boolean mAboveStatusBar;
     private final SystemUIDialog.Factory mSystemUIDialogFactory;
+    private final ShadeDialogContextInteractor mShadeDialogContextInteractor;
 
     @VisibleForTesting
     protected InternetAdapter mAdapter;
@@ -204,10 +205,11 @@
             @Main Handler handler,
             @Background Executor executor,
             KeyguardStateController keyguardStateController,
-            SystemUIDialog.Factory systemUIDialogFactory) {
-        mContext = context;
+            SystemUIDialog.Factory systemUIDialogFactory,
+            ShadeDialogContextInteractor shadeDialogContextInteractor) {
         mAboveStatusBar = aboveStatusBar;
         mSystemUIDialogFactory = systemUIDialogFactory;
+        mShadeDialogContextInteractor = shadeDialogContextInteractor;
         if (DEBUG) {
             Log.d(TAG, "Init InternetDialog");
         }
@@ -230,7 +232,8 @@
 
     @Override
     public SystemUIDialog createDialog() {
-        SystemUIDialog dialog = mSystemUIDialogFactory.create(this, mContext);
+        SystemUIDialog dialog = mSystemUIDialogFactory.create(this,
+                mShadeDialogContextInteractor.getContext());
         if (!mAboveStatusBar) {
             dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index 8c54ab40..862dba1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.qs.user
 
 import android.app.Dialog
-import android.content.Context
 import android.content.DialogInterface
 import android.content.DialogInterface.BUTTON_NEUTRAL
 import android.content.Intent
@@ -34,6 +33,7 @@
 import com.android.systemui.qs.QSUserSwitcherEvent
 import com.android.systemui.qs.tiles.UserDetailView
 import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.user.ui.dialog.DialogShowerImpl
 import javax.inject.Inject
@@ -50,6 +50,7 @@
     private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val uiEventLogger: UiEventLogger,
     private val dialogFactory: SystemUIDialog.Factory,
+    private val shadeDialogContextInteractor: ShadeDialogContextInteractor,
 ) {
 
     companion object {
@@ -63,7 +64,8 @@
      * Populate the dialog with information from and adapter obtained from
      * [userDetailViewAdapterProvider] and show it as launched from [expandable].
      */
-    fun showDialog(context: Context, expandable: Expandable) {
+    fun showDialog(expandable: Expandable) {
+        val context = shadeDialogContextInteractor.context
         with(dialogFactory.create(context)) {
             setShowForAllUsers(true)
             setCanceledOnTouchOutside(true)
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
index a7b51faa..10ac2cf 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.scrim;
 
+import static com.android.systemui.Flags.notificationShadeBlur;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
@@ -214,8 +216,7 @@
     public void draw(@NonNull Canvas canvas) {
         mPaint.setColor(mMainColor);
         mPaint.setAlpha(mAlpha);
-        if (WindowBlurFlag.isEnabled()) {
-            // TODO(b/370555223): Match the alpha to the visual spec when it is finalized.
+        if (notificationShadeBlur() || WindowBlurFlag.isEnabled()) {
             // TODO (b/381263600), wire this at ScrimController, move it to PrimaryBouncerTransition
             mPaint.setAlpha((int) (0.5f * mAlpha));
         }
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
index 4bfa61e..0f80e74 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.scrim;
 
+import static com.android.systemui.Flags.notificationShadeBlur;
+
 import static java.lang.Float.isNaN;
 
 import android.annotation.NonNull;
@@ -39,13 +41,12 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
+import com.android.systemui.res.R;
 import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.util.LargeScreenUtils;
 
 import java.util.concurrent.Executor;
 
-import static com.android.systemui.Flags.notificationShadeBlur;
-
 /**
  * A view which can draw a scrim.  This view maybe be used in multiple windows running on different
  * threads, but is controlled by {@link com.android.systemui.statusbar.phone.ScrimController} so we
@@ -253,8 +254,11 @@
                 mainTinted = ColorUtils.blendARGB(mColors.getMainColor(), mTintColor, tintAmount);
             }
             if (notificationShadeBlur()) {
-                // TODO(b/370555223): Fix color and transparency to match visual spec exactly
-                mainTinted = ColorUtils.blendARGB(mColors.getMainColor(), Color.GRAY, 0.5f);
+                int layerAbove = ColorUtils.setAlphaComponent(
+                        getResources().getColor(R.color.shade_panel, null),
+                        (int) (0.4f * 255));
+                int layerBelow = ColorUtils.setAlphaComponent(Color.WHITE, (int) (0.1f * 255));
+                mainTinted = ColorUtils.compositeColors(layerAbove, layerBelow);
             }
             drawable.setColor(mainTinted, animated);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index e1631cc..bbb13d5 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -61,9 +61,18 @@
     /** Callback for notifying of changes. */
     @WeaklyReferencedCallback
     interface Callback {
-        /** Notifies that the current user will be changed. */
+        /**
+         * Same as {@link onBeforeUserSwitching(Int, Runnable)} but the callback will be called
+         * automatically after the completion of this method.
+         */
         fun onBeforeUserSwitching(newUser: Int) {}
 
+        /** Notifies that the current user will be changed. */
+        fun onBeforeUserSwitching(newUser: Int, resultCallback: Runnable) {
+            onBeforeUserSwitching(newUser)
+            resultCallback.run()
+        }
+
         /**
          * Same as {@link onUserChanging(Int, Context, Runnable)} but the callback will be called
          * automatically after the completion of this method.
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index b7a3aed..42d8363 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -196,8 +196,9 @@
     private fun registerUserSwitchObserver() {
         iActivityManager.registerUserSwitchObserver(
             object : UserSwitchObserver() {
-                override fun onBeforeUserSwitching(newUserId: Int) {
+                override fun onBeforeUserSwitching(newUserId: Int, reply: IRemoteCallback?) {
                     handleBeforeUserSwitching(newUserId)
+                    reply?.sendResult(null)
                 }
 
                 override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) {
@@ -236,8 +237,7 @@
         setUserIdInternal(newUserId)
 
         notifySubscribers { callback, resultCallback ->
-                callback.onBeforeUserSwitching(newUserId)
-                resultCallback.run()
+                callback.onBeforeUserSwitching(newUserId, resultCallback)
             }
             .await()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index f2c3906..839d459 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -48,7 +48,6 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.MigrateClocksToBlueprint;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.shared.model.Edge;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
@@ -367,9 +366,7 @@
                     mTouchActive = true;
                     mTouchCancelled = false;
                     mDownEvent = ev;
-                    if (MigrateClocksToBlueprint.isEnabled()) {
-                        mService.userActivity();
-                    }
+                    mService.userActivity();
                 } else if (ev.getActionMasked() == MotionEvent.ACTION_UP
                         || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) {
                     mTouchActive = false;
@@ -443,8 +440,7 @@
                     float x = ev.getRawX();
                     float y = ev.getRawY();
                     if (mStatusBarViewController.touchIsWithinView(x, y)) {
-                        if (!(MigrateClocksToBlueprint.isEnabled()
-                                && mPrimaryBouncerInteractor.isBouncerShowing())) {
+                        if (!mPrimaryBouncerInteractor.isBouncerShowing()) {
                             if (mStatusBarWindowStateController.windowIsShowing()) {
                                 mIsTrackingBarGesture = true;
                                 return logDownDispatch(ev, "sending touch to status bar",
@@ -453,7 +449,7 @@
                                 return logDownDispatch(ev, "hidden or hiding", true);
                             }
                         } else {
-                            mShadeLogger.d("NSWVC: bouncer not showing");
+                            mShadeLogger.d("NSWVC: bouncer showing");
                         }
                     } else {
                         mShadeLogger.d("NSWVC: touch not within view");
@@ -511,34 +507,24 @@
                         && !bouncerShowing
                         && !mStatusBarStateController.isDozing()) {
                     if (mDragDownHelper.isDragDownEnabled()) {
-                        if (MigrateClocksToBlueprint.isEnabled()) {
-                            // When on lockscreen, if the touch originates at the top of the screen
-                            // go directly to QS and not the shade
-                            if (mStatusBarStateController.getState() == KEYGUARD
-                                    && mQuickSettingsController.shouldQuickSettingsIntercept(
-                                        ev.getX(), ev.getY(), 0)) {
-                                mShadeLogger.d("NSWVC: QS intercepted");
-                                return true;
-                            }
+                        // When on lockscreen, if the touch originates at the top of the screen go
+                        // directly to QS and not the shade
+                        if (mStatusBarStateController.getState() == KEYGUARD
+                                && mQuickSettingsController.shouldQuickSettingsIntercept(
+                                    ev.getX(), ev.getY(), 0)) {
+                            mShadeLogger.d("NSWVC: QS intercepted");
+                            return true;
                         }
 
                         // This handles drag down over lockscreen
                         boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
-                        if (MigrateClocksToBlueprint.isEnabled()) {
-                            if (result) {
-                                mLastInterceptWasDragDownHelper = true;
-                                if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-                                    mShadeLogger.d("NSWVC: drag down helper intercepted");
-                                }
-                            } else if (didNotificationPanelInterceptEvent(ev)) {
-                                return true;
+                        if (result) {
+                            mLastInterceptWasDragDownHelper = true;
+                            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                                mShadeLogger.d("NSWVC: drag down helper intercepted");
                             }
-                        } else {
-                            if (result) {
-                                if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-                                    mShadeLogger.d("NSWVC: drag down helper intercepted");
-                                }
-                            }
+                        } else if (didNotificationPanelInterceptEvent(ev)) {
+                            return true;
                         }
                         return result;
                     } else {
@@ -547,12 +533,10 @@
                             return true;
                         }
                     }
-                } else if (MigrateClocksToBlueprint.isEnabled()) {
+                } else if (!bouncerShowing && didNotificationPanelInterceptEvent(ev)) {
                     // This final check handles swipes on HUNs and when Pulsing
-                    if (!bouncerShowing && didNotificationPanelInterceptEvent(ev)) {
-                        mShadeLogger.d("NSWVC: intercepted for HUN/PULSING");
-                        return true;
-                    }
+                    mShadeLogger.d("NSWVC: intercepted for HUN/PULSING");
+                    return true;
                 }
                 return false;
             }
@@ -562,9 +546,6 @@
                 MotionEvent cancellation = MotionEvent.obtain(ev);
                 cancellation.setAction(MotionEvent.ACTION_CANCEL);
                 mStackScrollLayout.onInterceptTouchEvent(cancellation);
-                if (!MigrateClocksToBlueprint.isEnabled()) {
-                    mShadeViewController.handleExternalInterceptTouch(cancellation);
-                }
                 cancellation.recycle();
             }
 
@@ -574,22 +555,12 @@
                 if (mStatusBarStateController.isDozing()) {
                     handled = !mDozeServiceHost.isPulsing();
                 }
-                if (MigrateClocksToBlueprint.isEnabled()) {
-                    if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) {
-                        // we still want to finish our drag down gesture when locking the screen
-                        handled |= mDragDownHelper.onTouchEvent(ev) || handled;
-                    }
-                    if (!handled && mShadeViewController.handleExternalTouch(ev)) {
-                        return true;
-                    }
-                } else {
-                    if (mDragDownHelper.isDragDownEnabled()
-                            || mDragDownHelper.isDraggingDown()) {
-                        // we still want to finish our drag down gesture when locking the screen
-                        return mDragDownHelper.onTouchEvent(ev) || handled;
-                    } else {
-                        return handled;
-                    }
+                if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) {
+                    // we still want to finish our drag down gesture when locking the screen
+                    handled |= mDragDownHelper.onTouchEvent(ev) || handled;
+                }
+                if (!handled && mShadeViewController.handleExternalTouch(ev)) {
+                    return true;
                 }
                 return handled;
             }
@@ -673,14 +644,12 @@
     }
 
     private boolean didNotificationPanelInterceptEvent(MotionEvent ev) {
-        if (MigrateClocksToBlueprint.isEnabled()) {
-            // Since NotificationStackScrollLayout is now a sibling of notification_panel, we need
-            // to also ask NotificationPanelViewController directly, in order to process swipe up
-            // events originating from notifications
-            if (mShadeViewController.handleExternalInterceptTouch(ev)) {
-                mShadeLogger.d("NSWVC: NPVC intercepted");
-                return true;
-            }
+        // Since NotificationStackScrollLayout is now a sibling of notification_panel, we need to
+        // also ask NotificationPanelViewController directly, in order to process swipe up events
+        // originating from notifications
+        if (mShadeViewController.handleExternalInterceptTouch(ev)) {
+            mShadeLogger.d("NSWVC: NPVC intercepted");
+            return true;
         }
 
         return false;
@@ -707,9 +676,7 @@
         if (!SceneContainerFlag.isEnabled()) {
             mAmbientState.setSwipingUp(false);
         }
-        if (MigrateClocksToBlueprint.isEnabled()) {
-            mDragDownHelper.stopDragging();
-        }
+        mDragDownHelper.stopDragging();
     }
 
     private void setBrightnessMirrorShowingForDepth(boolean showing) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 207439e..5811157 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -21,7 +21,6 @@
 import android.view.WindowInsets
 import androidx.annotation.VisibleForTesting
 import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
 import androidx.constraintlayout.widget.ConstraintSet.END
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.START
@@ -32,7 +31,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.fragments.FragmentService
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.navigationbar.NavigationModeController
 import com.android.systemui.plugins.qs.QS
@@ -275,7 +273,6 @@
         constraintSet.clone(mView)
         setKeyguardStatusViewConstraints(constraintSet)
         setQsConstraints(constraintSet)
-        setNotificationsConstraints(constraintSet)
         setLargeScreenShadeHeaderConstraints(constraintSet)
         mView.applyConstraints(constraintSet)
     }
@@ -288,21 +285,6 @@
         }
     }
 
-    private fun setNotificationsConstraints(constraintSet: ConstraintSet) {
-        if (MigrateClocksToBlueprint.isEnabled) {
-            return
-        }
-        val startConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID
-        val nsslId = R.id.notification_stack_scroller
-        constraintSet.apply {
-            connect(nsslId, START, startConstraintId, START)
-            setMargin(nsslId, START, if (splitShadeEnabled) 0 else panelMarginHorizontal)
-            setMargin(nsslId, END, panelMarginHorizontal)
-            setMargin(nsslId, TOP, topMargin)
-            setMargin(nsslId, BOTTOM, notificationsBottomMargin)
-        }
-    }
-
     private fun setQsConstraints(constraintSet: ConstraintSet) {
         val endConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID
         constraintSet.apply {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index 1333055..000a666 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -21,7 +21,6 @@
 import android.app.Fragment;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
@@ -33,13 +32,10 @@
 import androidx.constraintlayout.widget.ConstraintSet;
 
 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
-import com.android.systemui.keyguard.MigrateClocksToBlueprint;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.AboveShelfObserver;
 
-import java.util.ArrayList;
-import java.util.Comparator;
 import java.util.function.Consumer;
 
 /**
@@ -50,11 +46,7 @@
 
     private View mQsFrame;
     private View mStackScroller;
-    private View mKeyguardStatusBar;
 
-    private final ArrayList<View> mDrawingOrderedChildren = new ArrayList<>();
-    private final ArrayList<View> mLayoutDrawingOrder = new ArrayList<>();
-    private final Comparator<View> mIndexComparator = Comparator.comparingInt(this::indexOfChild);
     private Consumer<WindowInsets> mInsetsChangedListener = insets -> {};
     private Consumer<QS> mQSFragmentAttachedListener = qs -> {};
     private QS mQs;
@@ -80,7 +72,6 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mQsFrame = findViewById(R.id.qs_frame);
-        mKeyguardStatusBar = findViewById(R.id.keyguard_header);
     }
 
     void setStackScroller(View stackScroller) {
@@ -160,46 +151,11 @@
     }
 
     @Override
-    protected void dispatchDraw(Canvas canvas) {
-        mDrawingOrderedChildren.clear();
-        mLayoutDrawingOrder.clear();
-        if (mKeyguardStatusBar.getVisibility() == View.VISIBLE) {
-            mDrawingOrderedChildren.add(mKeyguardStatusBar);
-            mLayoutDrawingOrder.add(mKeyguardStatusBar);
-        }
-        if (mQsFrame.getVisibility() == View.VISIBLE) {
-            mDrawingOrderedChildren.add(mQsFrame);
-            mLayoutDrawingOrder.add(mQsFrame);
-        }
-        if (mStackScroller.getVisibility() == View.VISIBLE) {
-            mDrawingOrderedChildren.add(mStackScroller);
-            mLayoutDrawingOrder.add(mStackScroller);
-        }
-
-        // Let's now find the order that the view has when drawing regularly by sorting
-        mLayoutDrawingOrder.sort(mIndexComparator);
-        super.dispatchDraw(canvas);
-    }
-
-    @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         return TouchLogger.logDispatchTouch("NotificationsQuickSettingsContainer", ev,
                 super.dispatchTouchEvent(ev));
     }
 
-    @Override
-    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
-        if (MigrateClocksToBlueprint.isEnabled()) {
-            return super.drawChild(canvas, child, drawingTime);
-        }
-        int layoutIndex = mLayoutDrawingOrder.indexOf(child);
-        if (layoutIndex >= 0) {
-            return super.drawChild(canvas, mDrawingOrderedChildren.get(layoutIndex), drawingTime);
-        } else {
-            return super.drawChild(canvas, child, drawingTime);
-        }
-    }
-
     public void applyConstraints(ConstraintSet constraintSet) {
         constraintSet.applyTo(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 0df2299..4fb43fd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -68,7 +68,6 @@
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.keyguard.MigrateClocksToBlueprint;
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
 import com.android.systemui.plugins.FalsingManager;
@@ -1828,16 +1827,6 @@
                             "onQsIntercept: down action, QS partially expanded/collapsed");
                     return true;
                 }
-                // TODO (b/265193930): remove dependency on NPVC
-                if (mPanelViewControllerLazy.get().isKeyguardShowing()
-                        && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
-                    // Dragging down on the lockscreen statusbar should prohibit other interactions
-                    // immediately, otherwise we'll wait on the touchslop. This is to allow
-                    // dragging down to expanded quick settings directly on the lockscreen.
-                    if (!MigrateClocksToBlueprint.isEnabled()) {
-                        mPanelView.getParent().requestDisallowInterceptTouchEvent(true);
-                    }
-                }
                 if (mExpansionAnimator != null) {
                     mInitialHeightOnTouch = mExpansionHeight;
                     mShadeLog.logMotionEvent(event,
@@ -1879,9 +1868,6 @@
                         && Math.abs(h) > Math.abs(x - mInitialTouchX)
                         && shouldQuickSettingsIntercept(
                         mInitialTouchX, mInitialTouchY, h)) {
-                    if (!MigrateClocksToBlueprint.isEnabled()) {
-                        mPanelView.getParent().requestDisallowInterceptTouchEvent(true);
-                    }
                     mShadeLog.onQsInterceptMoveQsTrackingEnabled(h);
                     setTracking(true);
                     traceQsJank(true, false);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index d31868c..61b9f08 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -35,6 +35,8 @@
 import com.android.systemui.res.R
 import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractorImpl
 import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
 import com.android.systemui.shade.data.repository.ShadeDisplaysRepositoryImpl
 import com.android.systemui.shade.display.ShadeDisplayPolicyModule
@@ -216,6 +218,25 @@
     }
 
     @Provides
+    @SysUISingleton
+    fun provideShadeDialogContextInteractor(
+        impl: ShadeDialogContextInteractorImpl
+    ): ShadeDialogContextInteractor = impl
+
+    @Provides
+    @IntoMap
+    @ClassKey(ShadeDialogContextInteractor::class)
+    fun provideShadeDialogContextInteractorCoreStartable(
+        impl: Provider<ShadeDialogContextInteractorImpl>
+    ): CoreStartable {
+        return if (ShadeWindowGoesAround.isEnabled) {
+            impl.get()
+        } else {
+            CoreStartable.NOP
+        }
+    }
+
+    @Provides
     @IntoMap
     @ClassKey(ShadePrimaryDisplayCommand::class)
     fun provideShadePrimaryDisplayCommand(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/FakeShadeDialogContextInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/FakeShadeDialogContextInteractor.kt
new file mode 100644
index 0000000..455370c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/FakeShadeDialogContextInteractor.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.shade.domain.interactor
+
+import android.content.Context
+
+/** Fake context repository that always returns the same context. */
+class FakeShadeDialogContextInteractor(override val context: Context) :
+    ShadeDialogContextInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt
new file mode 100644
index 0000000..201dc03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import android.content.Context
+import android.util.Log
+import android.view.Display
+import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL
+import com.android.app.tracing.coroutines.launchTraced
+import com.android.app.tracing.traceSection
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.filter
+
+/** Provides the correct context to show dialogs on the shade window, whenever it moves. */
+interface ShadeDialogContextInteractor {
+    /** Context usable to create dialogs on the notification shade display. */
+    val context: Context
+}
+
+@SysUISingleton
+class ShadeDialogContextInteractorImpl
+@Inject
+constructor(
+    @Main private val defaultContext: Context,
+    private val displayWindowPropertyRepository: Provider<DisplayWindowPropertiesRepository>,
+    private val shadeDisplaysRepository: ShadeDisplaysRepository,
+    @Background private val bgScope: CoroutineScope,
+) : CoreStartable, ShadeDialogContextInteractor {
+
+    override fun start() {
+        if (ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()) return
+        bgScope.launchTraced(TAG) {
+            shadeDisplaysRepository.displayId
+                // No need for default display pre-warming.
+                .filter { it != Display.DEFAULT_DISPLAY }
+                .collectLatest { displayId ->
+                    // Prewarms the context in the background every time the display changes.
+                    // In this way, there will be no main thread delays when a dialog is shown.
+                    getContextOrDefault(displayId)
+                }
+        }
+    }
+
+    override val context: Context
+        get() {
+            if (!ShadeWindowGoesAround.isEnabled) {
+                return defaultContext
+            }
+            val displayId = shadeDisplaysRepository.displayId.value
+            return getContextOrDefault(displayId)
+        }
+
+    private fun getContextOrDefault(displayId: Int): Context {
+        return try {
+            traceSection({ "Getting dialog context for displayId=$displayId" }) {
+                displayWindowPropertyRepository.get().get(displayId, DIALOG_WINDOW_TYPE).context
+            }
+        } catch (e: Exception) {
+            // This can happen if the display was disconnected in the meantime.
+            Log.e(
+                TAG,
+                "Couldn't get dialog context for displayId=$displayId. Returning default one",
+                e,
+            )
+            defaultContext
+        }
+    }
+
+    private companion object {
+        const val TAG = "ShadeDialogContextRepo"
+        const val DIALOG_WINDOW_TYPE = TYPE_STATUS_BAR_SUB_PANEL
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index b2ca33a..a7ad462 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -44,13 +44,11 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
 import com.android.internal.logging.UiEventLogger;
-import com.android.keyguard.KeyguardClockSwitch;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
 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;
@@ -136,7 +134,6 @@
     private HistoricalState[] mHistoricalRecords = new HistoricalState[HISTORY_SIZE];
     // These views are used by InteractionJankMonitor to get callback from HWUI.
     private View mView;
-    private KeyguardClockSwitch mClockSwitchView;
 
     /**
      * If any of the system bars is hidden.
@@ -426,7 +423,6 @@
         if ((mView == null || !mView.isAttachedToWindow())
                 && (view != null && view.isAttachedToWindow())) {
             mView = view;
-            mClockSwitchView = view.findViewById(R.id.keyguard_clock_container);
         }
         mDozeAmountTarget = dozeAmount;
         if (animated) {
@@ -511,16 +507,7 @@
 
     /** Returns the id of the currently rendering clock */
     public String getClockId() {
-        if (MigrateClocksToBlueprint.isEnabled()) {
-            return mKeyguardClockInteractorLazy.get().getRenderedClockId();
-        }
-
-        if (mClockSwitchView == null) {
-            Log.e(TAG, "Clock container was missing");
-            return KeyguardClockSwitch.MISSING_CLOCK_ID;
-        }
-
-        return mClockSwitchView.getClockId();
+        return mKeyguardClockInteractorLazy.get().getRenderedClockId();
     }
 
     private void beginInteractionJankMonitor() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 8a1371f..aa010cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -62,6 +62,7 @@
 import com.android.systemui.statusbar.notification.domain.NotificationDomainLayerModule;
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.notification.icon.ConversationIconManager;
 import com.android.systemui.statusbar.notification.icon.IconManager;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
@@ -78,8 +79,7 @@
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl;
 import com.android.systemui.statusbar.notification.logging.dagger.NotificationsLogModule;
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationLogger;
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProvider;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractorImpl;
 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
 import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactory;
 import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactoryLooperImpl;
@@ -92,7 +92,6 @@
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ZenModesCleanupStartable;
 
 import dagger.Binds;
@@ -105,8 +104,6 @@
 
 import kotlinx.coroutines.CoroutineScope;
 
-import java.util.Optional;
-
 import javax.inject.Provider;
 
 /**
@@ -315,21 +312,17 @@
     @ClassKey(ZenModesCleanupStartable.class)
     CoreStartable bindsZenModesCleanup(ZenModesCleanupStartable zenModesCleanup);
 
-    /**
-     * Provides {@link
-     * com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor} if
-     * one of the relevant feature flags is enabled.
-     */
+    /** Provides the default implementation of {@link PromotedNotificationContentExtractor} if at
+     * least one of the relevant feature flags is enabled, or an implementation that always returns
+     * null if none are enabled. */
     @Provides
     @SysUISingleton
-    static Optional<PromotedNotificationContentExtractor>
-            providePromotedNotificationContentExtractor(
-                    PromotedNotificationsProvider provider, Context context,
-                    PromotedNotificationLogger logger) {
+    static PromotedNotificationContentExtractor providesPromotedNotificationContentExtractor(
+            Provider<PromotedNotificationContentExtractorImpl> implProvider) {
         if (PromotedNotificationContentModel.featureFlagEnabled()) {
-            return Optional.of(new PromotedNotificationContentExtractor(provider, context, logger));
+            return implProvider.get();
         } else {
-            return Optional.empty();
+            return (entry, recoveredBuilder) -> null;
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
index 6756077..d02e17c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
@@ -1299,7 +1299,6 @@
         }
 
         private NotificationEntry requireEntry() {
-            /* check if */ SceneContainerFlag.isUnexpectedlyInLegacyMode();
             return Objects.requireNonNull(mEntry);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
index 863c665..4e9e333 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
@@ -34,15 +34,22 @@
 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When
 import javax.inject.Inject
 
+interface PromotedNotificationContentExtractor {
+    fun extractContent(
+        entry: NotificationEntry,
+        recoveredBuilder: Notification.Builder,
+    ): PromotedNotificationContentModel?
+}
+
 @SysUISingleton
-class PromotedNotificationContentExtractor
+class PromotedNotificationContentExtractorImpl
 @Inject
 constructor(
     private val promotedNotificationsProvider: PromotedNotificationsProvider,
     @ShadeDisplayAware private val context: Context,
     private val logger: PromotedNotificationLogger,
-) {
-    fun extractContent(
+) : PromotedNotificationContentExtractor {
+    override fun extractContent(
         entry: NotificationEntry,
         recoveredBuilder: Notification.Builder,
     ): PromotedNotificationContentModel? {
@@ -169,5 +176,5 @@
 
 private fun ProgressStyle.extractContent(contentBuilder: PromotedNotificationContentModel.Builder) {
     // TODO: Create NotificationProgressModel.toSkeleton, or something similar.
-    contentBuilder.progress = createProgressModel(0xffffffff.toInt(), 0x00000000)
+    contentBuilder.progress = createProgressModel(0xffffffff.toInt(), 0xff000000.toInt())
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 6e05e8e..6eeb80d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -921,7 +921,7 @@
         logger.logAsyncTaskProgress(entry, "finishing");
 
         if (PromotedNotificationContentModel.featureFlagEnabled()) {
-            entry.setPromotedNotificationContentModel(result.mExtractedPromotedNotificationContent);
+            entry.setPromotedNotificationContentModel(result.mPromotedContent);
         }
 
         boolean setRepliesAndActions = true;
@@ -1292,10 +1292,13 @@
 
             if (PromotedNotificationContentModel.featureFlagEnabled()) {
                 mLogger.logAsyncTaskProgress(mEntry, "extracting promoted notification content");
-                result.mExtractedPromotedNotificationContent = mPromotedNotificationContentExtractor
-                        .extractContent(mEntry, recoveredBuilder);
+                final PromotedNotificationContentModel promotedContent =
+                        mPromotedNotificationContentExtractor.extractContent(mEntry,
+                                recoveredBuilder);
                 mLogger.logAsyncTaskProgress(mEntry, "extracted promoted notification content: "
-                        + result.mExtractedPromotedNotificationContent);
+                        + promotedContent);
+
+                result.mPromotedContent = promotedContent;
             }
 
             mLogger.logAsyncTaskProgress(mEntry,
@@ -1399,7 +1402,7 @@
 
     @VisibleForTesting
     static class InflationProgress {
-        PromotedNotificationContentModel mExtractedPromotedNotificationContent;
+        PromotedNotificationContentModel mPromotedContent;
 
         private RemoteViews newContentView;
         private RemoteViews newHeadsUpView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index c7d80e9..7dcb2de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -16,7 +16,6 @@
 package com.android.systemui.statusbar.notification.row
 
 import android.annotation.SuppressLint
-import android.app.Flags
 import android.app.Notification
 import android.content.Context
 import android.content.ContextWrapper
@@ -591,7 +590,7 @@
         @VisibleForTesting val packageContext: Context,
         val remoteViews: NewRemoteViews,
         val contentModel: NotificationContentModel,
-        val extractedPromotedNotificationContentModel: PromotedNotificationContentModel?,
+        val promotedContent: PromotedNotificationContentModel?,
     ) {
 
         var inflatedContentView: View? = null
@@ -683,16 +682,15 @@
             promotedNotificationContentExtractor: PromotedNotificationContentExtractor,
             logger: NotificationRowContentBinderLogger,
         ): InflationProgress {
-            val promoted =
+            val promotedContent =
                 if (PromotedNotificationContentModel.featureFlagEnabled()) {
                     logger.logAsyncTaskProgress(entry, "extracting promoted notification content")
-                    val extracted =
-                        promotedNotificationContentExtractor.extractContent(entry, builder)
-                    logger.logAsyncTaskProgress(
-                        entry,
-                        "extracted promoted notification content: {extracted}",
-                    )
-                    extracted
+                    promotedNotificationContentExtractor.extractContent(entry, builder).also {
+                        logger.logAsyncTaskProgress(
+                            entry,
+                            "extracted promoted notification content: $it",
+                        )
+                    }
                 } else {
                     null
                 }
@@ -759,7 +757,7 @@
                 packageContext = packageContext,
                 remoteViews = remoteViews,
                 contentModel = contentModel,
-                extractedPromotedNotificationContentModel = promoted,
+                promotedContent = promotedContent,
             )
         }
 
@@ -1420,8 +1418,7 @@
 
             entry.setContentModel(result.contentModel)
             if (PromotedNotificationContentModel.featureFlagEnabled()) {
-                entry.promotedNotificationContentModel =
-                    result.extractedPromotedNotificationContentModel
+                entry.promotedNotificationContentModel = result.promotedContent
             }
 
             result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) }
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 7c9d850..38a7035 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
@@ -3729,14 +3729,6 @@
 
     // Only when scene container is enabled, mark that we are being dragged so that we start
     // dispatching the rest of the gesture to scene container.
-    void startOverscrollAfterExpanding() {
-        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
-        getExpandHelper().finishExpanding();
-        setIsBeingDragged(true);
-    }
-
-    // Only when scene container is enabled, mark that we are being dragged so that we start
-    // dispatching the rest of the gesture to scene container.
     void startDraggingOnHun() {
         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
         setIsBeingDragged(true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 245b1d2..a33a9ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -2203,11 +2203,10 @@
                 expandingNotification = mView.isExpandingNotification();
                 if (mView.getExpandedInThisMotion() && !expandingNotification && wasExpandingBefore
                         && !mView.getDisallowScrollingInThisMotion()) {
-                    // We need to dispatch the overscroll differently when Scene Container is on,
-                    // since NSSL no longer controls its own scroll.
+                    // Finish expansion here, as this gesture will be marked to be sent to
+                    // scene container
                     if (SceneContainerFlag.isEnabled() && !isCancelOrUp) {
-                        mView.startOverscrollAfterExpanding();
-                        return true;
+                        expandHelper.finishExpanding();
                     } else {
                         mView.dispatchDownEventToScroller(ev);
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 7bea480..6dc25aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -21,9 +21,10 @@
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.app.StatusBarManager.WindowVisibleState;
 import static android.app.StatusBarManager.windowStateToString;
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO;
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
 
-import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
-import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
 import static androidx.lifecycle.Lifecycle.State.RESUMED;
 
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
@@ -207,6 +208,7 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -221,7 +223,6 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.statusbar.policy.ExtensionController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
@@ -2514,12 +2515,15 @@
      * should update only the status bar components.
      */
     private void setBouncerShowingForStatusBarComponents(boolean bouncerShowing) {
-        int importance = bouncerShowing
-                ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                : IMPORTANT_FOR_ACCESSIBILITY_AUTO;
         if (!StatusBarConnectedDisplays.isEnabled() && mPhoneStatusBarViewController != null) {
+            int importance = bouncerShowing
+                    ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                    : IMPORTANT_FOR_ACCESSIBILITY_AUTO;
             mPhoneStatusBarViewController.setImportantForAccessibility(importance);
         }
+        int importance = bouncerShowing
+                ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                : IMPORTANT_FOR_ACCESSIBILITY_NO;
         mShadeSurface.setImportantForAccessibility(importance);
         mShadeSurface.setBouncerShowing(bouncerShowing);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 78954de..8b60ee5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -192,7 +192,7 @@
             mUiEventLogger.log(
                     LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP);
 
-            mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground.getContext(),
+            mUserSwitchDialogController.showDialog(
                     Expandable.fromView(mUserAvatarViewWithBackground));
         });
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
index a1d5cbe..9ff0d18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
@@ -44,6 +44,7 @@
 import com.android.systemui.dialog.ui.composable.AlertDialogContent
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
 import com.android.systemui.statusbar.phone.ComponentSystemUIDialog
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.SystemUIDialogFactory
@@ -67,6 +68,7 @@
     private val viewModel: Provider<ModesDialogViewModel>,
     private val dialogEventLogger: ModesDialogEventLogger,
     @Main private val mainCoroutineContext: CoroutineContext,
+    private val shadeDisplayContextRepository: ShadeDialogContextInteractor,
 ) : SystemUIDialog.Delegate {
     // NOTE: This should only be accessed/written from the main thread.
     @VisibleForTesting var currentDialog: ComponentSystemUIDialog? = null
@@ -78,7 +80,10 @@
             currentDialog?.dismiss()
         }
 
-        currentDialog = sysuiDialogFactory.create { ModesDialogContent(it) }
+        currentDialog =
+            sysuiDialogFactory.create(context = shadeDisplayContextRepository.context) {
+                ModesDialogContent(it)
+            }
         currentDialog
             ?.lifecycle
             ?.addObserver(
@@ -106,9 +111,8 @@
                 modifier =
                     Modifier.semantics {
                         testTagsAsResourceId = true
-                        paneTitle = dialog.context.getString(
-                            R.string.accessibility_desc_quick_settings
-                        )
+                        paneTitle =
+                            dialog.context.getString(R.string.accessibility_desc_quick_settings)
                     },
                 title = {
                     Text(
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index 102fcc0..e4b2dc2 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -18,7 +18,6 @@
 package com.android.systemui.user.ui.dialog
 
 import android.app.Dialog
-import android.content.Context
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.UiEventLogger
 import com.android.settingslib.users.UserCreatingDialog
@@ -32,6 +31,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
 import com.android.systemui.user.UserSwitchFullscreenDialog
 import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
 import com.android.systemui.user.domain.model.ShowDialogRequestModel
@@ -48,7 +48,6 @@
 class UserSwitcherDialogCoordinator
 @Inject
 constructor(
-    @Application private val context: Lazy<Context>,
     @Application private val applicationScope: Lazy<CoroutineScope>,
     private val falsingManager: Lazy<FalsingManager>,
     private val broadcastSender: Lazy<BroadcastSender>,
@@ -59,6 +58,7 @@
     private val activityStarter: Lazy<ActivityStarter>,
     private val falsingCollector: Lazy<FalsingCollector>,
     private val userSwitcherViewModel: Lazy<UserSwitcherViewModel>,
+    private val shadeDialogContextInteractor: Lazy<ShadeDialogContextInteractor>,
 ) : CoreStartable {
 
     private var currentDialog: Dialog? = null
@@ -71,12 +71,13 @@
     private fun startHandlingDialogShowRequests() {
         applicationScope.get().launch {
             interactor.get().dialogShowRequests.filterNotNull().collect { request ->
+                val context = shadeDialogContextInteractor.get().context
                 val (dialog, dialogCuj) =
                     when (request) {
                         is ShowDialogRequestModel.ShowAddUserDialog ->
                             Pair(
                                 AddUserDialog(
-                                    context = context.get(),
+                                    context = context,
                                     userHandle = request.userHandle,
                                     isKeyguardShowing = request.isKeyguardShowing,
                                     showEphemeralMessage = request.showEphemeralMessage,
@@ -92,7 +93,7 @@
                         is ShowDialogRequestModel.ShowUserCreationDialog ->
                             Pair(
                                 UserCreatingDialog(
-                                    context.get(),
+                                    context,
                                     request.isGuest,
                                 ),
                                 null,
@@ -100,7 +101,7 @@
                         is ShowDialogRequestModel.ShowExitGuestDialog ->
                             Pair(
                                 ExitGuestDialog(
-                                    context = context.get(),
+                                    context = context,
                                     guestUserId = request.guestUserId,
                                     isGuestEphemeral = request.isGuestEphemeral,
                                     targetUserId = request.targetUserId,
@@ -117,7 +118,7 @@
                         is ShowDialogRequestModel.ShowUserSwitcherDialog ->
                             Pair(
                                 UserSwitchDialog(
-                                    context = context.get(),
+                                    context = context,
                                     adapter = userDetailAdapterProvider.get(),
                                     uiEventLogger = eventLogger.get(),
                                     falsingManager = falsingManager.get(),
@@ -132,7 +133,7 @@
                         is ShowDialogRequestModel.ShowUserSwitcherFullscreenDialog ->
                             Pair(
                                 UserSwitchFullscreenDialog(
-                                    context = context.get(),
+                                    context = context,
                                     falsingCollector = falsingCollector.get(),
                                     userSwitcherViewModel = userSwitcherViewModel.get(),
                                 ),
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt
index 2e1f82d..70e342f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt
@@ -17,45 +17,28 @@
 package com.android.systemui.volume.dialog.settings.ui.binder
 
 import android.view.View
-import com.android.systemui.lifecycle.WindowLifecycleState
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.lifecycle.setSnapshotBinding
-import com.android.systemui.lifecycle.viewModel
+import android.widget.ImageButton
 import com.android.systemui.res.R
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
 import com.android.systemui.volume.dialog.settings.ui.viewmodel.VolumeDialogSettingsButtonViewModel
 import javax.inject.Inject
-import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 
 @VolumeDialogScope
 class VolumeDialogSettingsButtonViewBinder
 @Inject
-constructor(private val viewModelFactory: VolumeDialogSettingsButtonViewModel.Factory) {
+constructor(private val viewModel: VolumeDialogSettingsButtonViewModel) {
 
-    fun bind(view: View) {
-        with(view) {
-            val button = requireViewById<View>(R.id.volume_dialog_settings)
-            repeatWhenAttached {
-                viewModel(
-                    traceName = "VolumeDialogViewBinder",
-                    minWindowLifecycleState = WindowLifecycleState.ATTACHED,
-                    factory = { viewModelFactory.create() },
-                ) { viewModel ->
-                    setSnapshotBinding {
-                        viewModel.isVisible
-                            .onEach { isVisible ->
-                                visibility = if (isVisible) View.VISIBLE else View.GONE
-                            }
-                            .launchIn(this)
+    fun CoroutineScope.bind(view: View) {
+        val button = view.requireViewById<ImageButton>(R.id.volume_dialog_settings)
+        viewModel.isVisible
+            .onEach { isVisible -> button.visibility = if (isVisible) View.VISIBLE else View.GONE }
+            .launchIn(this)
 
-                        button.setOnClickListener { viewModel.onButtonClicked() }
-                    }
+        viewModel.icon.onEach { button.setImageDrawable(it) }.launchIn(this)
 
-                    awaitCancellation()
-                }
-            }
-        }
+        button.setOnClickListener { viewModel.onButtonClicked() }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt
index 015d773..03442db 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt
@@ -14,27 +14,206 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.volume.dialog.settings.ui.viewmodel
 
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.ColorFilter
+import android.graphics.drawable.Drawable
+import android.media.session.PlaybackState
+import androidx.annotation.ColorInt
+import com.airbnb.lottie.LottieComposition
+import com.airbnb.lottie.LottieCompositionFactory
+import com.airbnb.lottie.LottieDrawable
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.SimpleColorFilter
+import com.airbnb.lottie.model.KeyPath
+import com.airbnb.lottie.value.LottieValueCallback
+import com.android.internal.R as internalR
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.UiBackground
+import com.android.systemui.lottie.await
+import com.android.systemui.res.R
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
 import com.android.systemui.volume.dialog.settings.domain.VolumeDialogSettingsButtonInteractor
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+import com.android.systemui.volume.panel.shared.model.filterData
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.resume
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.FlowCollector
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.runningFold
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transform
+import kotlinx.coroutines.suspendCancellableCoroutine
 
 class VolumeDialogSettingsButtonViewModel
-@AssistedInject
-constructor(private val interactor: VolumeDialogSettingsButtonInteractor) {
+@Inject
+constructor(
+    @Application private val context: Context,
+    @UiBackground private val uiBgCoroutineContext: CoroutineContext,
+    @VolumeDialog private val coroutineScope: CoroutineScope,
+    mediaOutputInteractor: MediaOutputInteractor,
+    private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
+    private val interactor: VolumeDialogSettingsButtonInteractor,
+) {
+
+    @SuppressLint("UseCompatLoadingForDrawables")
+    private val drawables: Flow<Drawables> =
+        flow {
+                val color = context.getColor(internalR.color.materialColorPrimary)
+                emit(
+                    Drawables(
+                        start =
+                            LottieCompositionFactory.fromRawRes(context, R.raw.audio_bars_in)
+                                .await()
+                                .toDrawable { setColor(color) },
+                        playing =
+                            LottieCompositionFactory.fromRawRes(context, R.raw.audio_bars_playing)
+                                .await()
+                                .toDrawable {
+                                    repeatCount = LottieDrawable.INFINITE
+                                    repeatMode = LottieDrawable.RESTART
+                                    setColor(color)
+                                },
+                        stop =
+                            LottieCompositionFactory.fromRawRes(context, R.raw.audio_bars_out)
+                                .await()
+                                .toDrawable { setColor(color) },
+                        idle = context.getDrawable(R.drawable.audio_bars_idle)!!,
+                    )
+                )
+            }
+            .buffer()
+            .flowOn(uiBgCoroutineContext)
+            .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+            .filterNotNull()
 
     val isVisible = interactor.isVisible
+    val icon: Flow<Drawable> =
+        mediaOutputInteractor.defaultActiveMediaSession
+            .filterData()
+            .flatMapLatest { session ->
+                if (session == null) {
+                    flowOf(null)
+                } else {
+                    mediaDeviceSessionInteractor.playbackState(session)
+                }
+            }
+            .runningFold(null) { playbackStates: PlaybackStates?, playbackState: PlaybackState? ->
+                val isCurrentActive = playbackState?.isActive ?: false
+                if (playbackStates != null && isCurrentActive == playbackState?.isActive) {
+                    return@runningFold playbackStates
+                }
+                playbackStates?.copy(
+                    isPreviousActive = playbackStates.isCurrentActive,
+                    isCurrentActive = isCurrentActive,
+                ) ?: PlaybackStates(isPreviousActive = null, isCurrentActive = isCurrentActive)
+            }
+            .filterNotNull()
+            // only apply the most recent state if we wait for the animation.
+            .buffer(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+            // distinct again because the changed state might've been dropped by the buffer
+            .distinctUntilChangedBy { it.isCurrentActive }
+            .transform { emitDrawables(it) }
+            .runningFold(null) { previous: Drawable?, current: Drawable ->
+                // wait for the previous animation to finish before starting the new one
+                // this also waits for the current loop of the playing animation to finish
+                (previous as? LottieDrawable)?.awaitFinish()
+                (current as? LottieDrawable)?.start()
+                current
+            }
+            .filterNotNull()
+
+    private suspend fun FlowCollector<Drawable>.emitDrawables(playbackStates: PlaybackStates) {
+        val animations = drawables.first()
+        val stateChanged =
+            playbackStates.isPreviousActive != null &&
+                playbackStates.isPreviousActive != playbackStates.isCurrentActive
+        if (playbackStates.isCurrentActive) {
+            if (stateChanged) {
+                emit(animations.start)
+            }
+            emit(animations.playing)
+        } else {
+            if (stateChanged) {
+                emit(animations.stop)
+            }
+            emit(animations.idle)
+        }
+    }
 
     fun onButtonClicked() {
         interactor.onButtonClicked()
     }
 
-    @VolumeDialogScope
-    @AssistedFactory
-    interface Factory {
+    private data class PlaybackStates(val isPreviousActive: Boolean?, val isCurrentActive: Boolean)
 
-        fun create(): VolumeDialogSettingsButtonViewModel
+    private data class Drawables(
+        val start: LottieDrawable,
+        val playing: LottieDrawable,
+        val stop: LottieDrawable,
+        val idle: Drawable,
+    )
+}
+
+private fun LottieComposition.toDrawable(setup: LottieDrawable.() -> Unit = {}): LottieDrawable =
+    LottieDrawable().also { drawable ->
+        drawable.composition = this
+        drawable.setup()
     }
+
+/** Suspends until current loop of the repeating animation is finished */
+private suspend fun LottieDrawable.awaitFinish() = suspendCancellableCoroutine { continuation ->
+    if (!isRunning) {
+        continuation.resume(Unit)
+        return@suspendCancellableCoroutine
+    }
+    val listener =
+        object : AnimatorListenerAdapter() {
+            override fun onAnimationRepeat(animation: Animator) {
+                continuation.resume(Unit)
+                removeAnimatorListener(this)
+            }
+
+            override fun onAnimationEnd(animation: Animator) {
+                continuation.resume(Unit)
+                removeAnimatorListener(this)
+            }
+
+            override fun onAnimationCancel(animation: Animator) {
+                continuation.resume(Unit)
+                removeAnimatorListener(this)
+            }
+        }
+    addAnimatorListener(listener)
+    continuation.invokeOnCancellation { removeAnimatorListener(listener) }
+}
+
+/**
+ * Overrides colors of the [LottieDrawable] to a specified [color]
+ *
+ * @see com.airbnb.lottie.LottieAnimationView
+ */
+private fun LottieDrawable.setColor(@ColorInt color: Int) {
+    val callback = LottieValueCallback<ColorFilter>(SimpleColorFilter(color))
+    addValueCallback(KeyPath("**"), LottieProperty.COLOR_FILTER, callback)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index f305246..faf06b94 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderStateModel
 import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
 import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
-import com.android.systemui.volume.dialog.ui.utils.awaitAnimation
+import com.android.systemui.volume.dialog.ui.utils.suspendAnimate
 import com.google.android.material.slider.LabelFormatter
 import com.google.android.material.slider.Slider
 import javax.inject.Inject
@@ -84,5 +84,5 @@
             interpolator = DecelerateInterpolator()
             addListener(jankListener)
         }
-        .awaitAnimation<Float> { value = it }
+        .suspendAnimate<Float> { value = it }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
index 10cf615..5f12480 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
@@ -22,6 +22,7 @@
 import android.view.ViewPropertyAnimator
 import androidx.dynamicanimation.animation.DynamicAnimation
 import androidx.dynamicanimation.animation.SpringAnimation
+import com.airbnb.lottie.LottieDrawable
 import kotlin.coroutines.resume
 import kotlinx.coroutines.CancellableContinuation
 import kotlinx.coroutines.suspendCancellableCoroutine
@@ -66,7 +67,7 @@
  * is cancelled.
  */
 @Suppress("UNCHECKED_CAST")
-suspend fun <T> ValueAnimator.awaitAnimation(onValueChanged: (T) -> Unit) {
+suspend fun <T> ValueAnimator.suspendAnimate(onValueChanged: (T) -> Unit) {
     suspendCancellableCoroutine { continuation ->
         addListener(
             object : AnimatorListenerAdapter() {
@@ -103,6 +104,29 @@
     }
 }
 
+/**
+ * Starts the animation and suspends until it's finished. Cancels the animation if the running
+ * coroutine is cancelled.
+ */
+suspend fun LottieDrawable.suspendAnimate() = suspendCancellableCoroutine { continuation ->
+    val listener =
+        object : AnimatorListenerAdapter() {
+            override fun onAnimationEnd(animation: Animator) {
+                continuation.resumeIfCan(Unit)
+            }
+
+            override fun onAnimationCancel(animation: Animator) {
+                continuation.resumeIfCan(Unit)
+            }
+        }
+    addAnimatorListener(listener)
+    start()
+    continuation.invokeOnCancellation {
+        removeAnimatorListener(listener)
+        stop()
+    }
+}
+
 private fun <T> CancellableContinuation<T>.resumeIfCan(value: T) {
     if (!isCancelled && !isCompleted) {
         resume(value)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
index 6e1ebc8..12e624ca 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
@@ -19,10 +19,10 @@
 import android.media.session.MediaController
 import android.media.session.PlaybackState
 import com.android.settingslib.volume.data.repository.MediaControllerRepository
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel
 import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
-import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -38,7 +38,7 @@
 
 /** Allows to observe and change [MediaDeviceSession] state. */
 @OptIn(ExperimentalCoroutinesApi::class)
-@VolumePanelScope
+@SysUISingleton
 class MediaDeviceSessionInteractor
 @Inject
 constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
index b3848a6..2973e11 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -24,12 +24,13 @@
 import com.android.settingslib.media.MediaDevice
 import com.android.settingslib.volume.data.repository.LocalMediaRepository
 import com.android.settingslib.volume.data.repository.MediaControllerRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.util.concurrency.Execution
 import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
 import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSessions
 import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
-import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import com.android.systemui.volume.panel.shared.model.Result
 import com.android.systemui.volume.panel.shared.model.filterData
 import com.android.systemui.volume.panel.shared.model.wrapInResult
@@ -54,13 +55,13 @@
 
 /** Provides observable models about the current media session state. */
 @OptIn(ExperimentalCoroutinesApi::class)
-@VolumePanelScope
+@SysUISingleton
 class MediaOutputInteractor
 @Inject
 constructor(
     private val localMediaRepositoryFactory: LocalMediaRepositoryFactory,
     private val packageManager: PackageManager,
-    @VolumePanelScope private val coroutineScope: CoroutineScope,
+    @Application private val coroutineScope: CoroutineScope,
     @Background private val backgroundCoroutineContext: CoroutineContext,
     mediaControllerRepository: MediaControllerRepository,
     private val mediaControllerInteractor: MediaControllerInteractor,
@@ -77,7 +78,7 @@
                     .onStart { emit(activeSessions) }
             }
             .map { getMediaControllers(it) }
-            .stateIn(coroutineScope, SharingStarted.Eagerly, MediaControllers(null, null))
+            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), MediaControllers(null, null))
 
     /** [MediaDeviceSessions] that contains currently active sessions. */
     val activeMediaDeviceSessions: Flow<MediaDeviceSessions> =
@@ -89,7 +90,11 @@
                 )
             }
             .flowOn(backgroundCoroutineContext)
-            .stateIn(coroutineScope, SharingStarted.Eagerly, MediaDeviceSessions(null, null))
+            .stateIn(
+                coroutineScope,
+                SharingStarted.WhileSubscribed(),
+                MediaDeviceSessions(null, null),
+            )
 
     /** Returns the default [MediaDeviceSession] from [activeMediaDeviceSessions] */
     val defaultActiveMediaSession: StateFlow<Result<MediaDeviceSession?>> =
@@ -104,7 +109,7 @@
             }
             .wrapInResult()
             .flowOn(backgroundCoroutineContext)
-            .stateIn(coroutineScope, SharingStarted.Eagerly, Result.Loading())
+            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), Result.Loading())
 
     private val localMediaRepository: Flow<LocalMediaRepository> =
         defaultActiveMediaSession
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index a41725f..4abbbac 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -25,7 +25,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND_INACTIVE
-import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.flags.Flags
@@ -299,20 +298,6 @@
         }
 
     @Test
-    @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    fun keyguardCallback_visibilityChanged_clockDozeCalled() =
-        runBlocking(IMMEDIATE) {
-            val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
-            verify(keyguardUpdateMonitor).registerCallback(capture(captor))
-
-            captor.value.onKeyguardVisibilityChanged(true)
-            verify(animations, never()).doze(0f)
-
-            captor.value.onKeyguardVisibilityChanged(false)
-            verify(animations, times(2)).doze(0f)
-        }
-
-    @Test
     fun keyguardCallback_timeFormat_clockNotified() =
         runBlocking(IMMEDIATE) {
             val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
@@ -344,19 +329,6 @@
         }
 
     @Test
-    fun keyguardCallback_verifyKeyguardChanged() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.listenForDozeAmount(this)
-            repository.setDozeAmount(0.4f)
-
-            yield()
-
-            verify(animations, times(2)).doze(0.4f)
-
-            job.cancel()
-        }
-
-    @Test
     fun listenForDozeAmountTransition_updatesClockDozeAmount() =
         runBlocking(IMMEDIATE) {
             val transitionStep = MutableStateFlow(TransitionStep())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
index fb0fd23..6bfd080 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
@@ -34,8 +34,10 @@
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.model.SysUiState
 import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.shadeDialogContextInteractor
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
@@ -82,7 +84,7 @@
     private val uiProperties =
         BluetoothTileDialogViewModel.UiProperties.build(
             isBluetoothEnabled = ENABLED,
-            isAutoOnToggleFeatureAvailable = ENABLED
+            isAutoOnToggleFeatureAvailable = ENABLED,
         )
     @Mock private lateinit var sysuiDialogFactory: SystemUIDialog.Factory
     @Mock private lateinit var dialogManager: SystemUIDialogManager
@@ -98,6 +100,8 @@
     private lateinit var mBluetoothTileDialogDelegate: BluetoothTileDialogDelegate
     private lateinit var deviceItem: DeviceItem
 
+    private val kosmos = testKosmos()
+
     @Before
     fun setUp() {
         scheduler = TestCoroutineScheduler()
@@ -116,10 +120,16 @@
                 fakeSystemClock,
                 uiEventLogger,
                 logger,
-                sysuiDialogFactory
+                sysuiDialogFactory,
+                kosmos.shadeDialogContextInteractor,
             )
 
-        whenever(sysuiDialogFactory.create(any(SystemUIDialog.Delegate::class.java))).thenAnswer {
+        whenever(
+            sysuiDialogFactory.create(
+                any(SystemUIDialog.Delegate::class.java),
+                any()
+            )
+        ).thenAnswer {
             SystemUIDialog(
                 mContext,
                 0,
@@ -128,7 +138,7 @@
                 sysuiState,
                 fakeBroadcastDispatcher,
                 dialogTransitionAnimator,
-                it.getArgument(0)
+                it.getArgument(0),
             )
         }
 
@@ -140,7 +150,7 @@
                 deviceName = DEVICE_NAME,
                 connectionSummary = DEVICE_CONNECTION_SUMMARY,
                 iconWithDescription = icon,
-                background = null
+                background = null,
             )
         `when`(cachedBluetoothDevice.isBusy).thenReturn(false)
     }
@@ -169,7 +179,7 @@
                 dialog,
                 listOf(deviceItem),
                 showSeeAll = false,
-                showPairNewDevice = false
+                showPairNewDevice = false,
             )
 
             val recyclerView = dialog.requireViewById<RecyclerView>(R.id.device_list)
@@ -217,6 +227,7 @@
                     uiEventLogger,
                     logger,
                     sysuiDialogFactory,
+                    kosmos.shadeDialogContextInteractor,
                 )
                 .Adapter(bluetoothTileDialogCallback)
                 .DeviceItemViewHolder(view)
@@ -238,7 +249,7 @@
                 dialog,
                 listOf(deviceItem),
                 showSeeAll = false,
-                showPairNewDevice = true
+                showPairNewDevice = true,
             )
 
             val seeAllButton = dialog.requireViewById<View>(R.id.see_all_button)
@@ -272,6 +283,7 @@
                         uiEventLogger,
                         logger,
                         sysuiDialogFactory,
+                        kosmos.shadeDialogContextInteractor,
                     )
                     .createDialog()
             dialog.show()
@@ -295,6 +307,7 @@
                         uiEventLogger,
                         logger,
                         sysuiDialogFactory,
+                        kosmos.shadeDialogContextInteractor,
                     )
                     .createDialog()
             dialog.show()
@@ -318,6 +331,7 @@
                         uiEventLogger,
                         logger,
                         sysuiDialogFactory,
+                        kosmos.shadeDialogContextInteractor,
                     )
                     .createDialog()
             dialog.show()
@@ -339,7 +353,7 @@
                 dialog,
                 visibility = VISIBLE,
                 label = null,
-                isActive = true
+                isActive = true,
             )
 
             val audioSharingButton = dialog.requireViewById<View>(R.id.audio_sharing_button)
@@ -361,7 +375,7 @@
                 dialog,
                 visibility = VISIBLE,
                 label = null,
-                isActive = false
+                isActive = false,
             )
 
             val audioSharingButton = dialog.requireViewById<View>(R.id.audio_sharing_button)
@@ -383,7 +397,7 @@
                 dialog,
                 visibility = GONE,
                 label = null,
-                isActive = false
+                isActive = false,
             )
 
             val audioSharingButton = dialog.requireViewById<View>(R.id.audio_sharing_button)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
similarity index 97%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch
rename to packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
index c2c94a8..1cabf20 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
@@ -77,6 +77,8 @@
                 INITIATION_WIDTH,
                 mKosmos.getCommunalInteractor(),
                 mKosmos.getConfigurationInteractor(),
+                mKosmos.getSceneInteractor(),
+                Optional.of(mKosmos.getMockWindowRootViewProvider()),
                 mLifecycle
                 );
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
index 1184a76..eb19a9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
@@ -81,7 +81,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @FlakyTest(
     bugId = 292574995,
-    detail = "on certain architectures all permutations with startActivity=true is causing failures"
+    detail = "on certain architectures all permutations with startActivity=true is causing failures",
 )
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
@@ -93,11 +93,7 @@
         private val DRAWABLE =
             mock<Icon> {
                 whenever(this.contentDescription)
-                    .thenReturn(
-                        ContentDescription.Resource(
-                            res = CONTENT_DESCRIPTION_RESOURCE_ID,
-                        )
-                    )
+                    .thenReturn(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
             }
         private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
 
@@ -273,13 +269,7 @@
                 context = context,
                 userFileManager =
                     mock<UserFileManager>().apply {
-                        whenever(
-                                getSharedPreferences(
-                                    anyString(),
-                                    anyInt(),
-                                    anyInt(),
-                                )
-                            )
+                        whenever(getSharedPreferences(anyString(), anyInt(), anyInt()))
                             .thenReturn(FakeSharedPreferences())
                     },
                 userTracker = userTracker,
@@ -316,9 +306,7 @@
         underTest =
             KeyguardQuickAffordanceInteractor(
                 keyguardInteractor =
-                    KeyguardInteractorFactory.create(
-                            featureFlags = featureFlags,
-                        )
+                    KeyguardInteractorFactory.create(featureFlags = featureFlags)
                         .keyguardInteractor,
                 shadeInteractor = kosmos.shadeInteractor,
                 lockPatternUtils = lockPatternUtils,
@@ -350,9 +338,7 @@
 
             homeControls.setState(
                 lockScreenState =
-                    KeyguardQuickAffordanceConfig.LockScreenState.Visible(
-                        icon = DRAWABLE,
-                    )
+                    KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = DRAWABLE)
             )
             homeControls.onTriggeredResult =
                 if (startActivity) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
index b26f0a6..782b248 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
@@ -557,6 +557,13 @@
     }
 
     @Test
+    public void startActivityForDialog_always_startActivityWithoutDismissShade() {
+        mInternetDialogController.startActivityForDialog(mock(Intent.class));
+
+        verify(mActivityStarter).startActivity(any(Intent.class), eq(false) /* dismissShade */);
+    }
+
+    @Test
     public void launchWifiDetailsSetting_withNoWifiEntryKey_doNothing() {
         mInternetDialogController.launchWifiDetailsSetting(null /* key */, mDialogLaunchView);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
index 300c9b8..8560b67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
@@ -35,6 +35,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogTransitionAnimator;
 import com.android.systemui.res.R;
+import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -149,7 +150,8 @@
                 mHandler,
                 mBgExecutor,
                 mKeyguard,
-                mSystemUIDialogFactory);
+                mSystemUIDialogFactory,
+                new FakeShadeDialogContextInteractor(mContext));
         mInternetDialogDelegate.createDialog();
         mInternetDialogDelegate.onCreate(mSystemUIDialog, null);
         mInternetDialogDelegate.mAdapter = mInternetAdapter;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index a0ecb80..f695c13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -76,6 +76,8 @@
 
     @Mock private lateinit var iActivityManager: IActivityManager
 
+    @Mock private lateinit var beforeUserSwitchingReply: IRemoteCallback
+
     @Mock private lateinit var userSwitchingReply: IRemoteCallback
 
     @Mock(stubOnly = true) private lateinit var dumpManager: DumpManager
@@ -199,9 +201,10 @@
 
             val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
             verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
-            captor.value.onBeforeUserSwitching(newID)
+            captor.value.onBeforeUserSwitching(newID, beforeUserSwitchingReply)
             captor.value.onUserSwitching(newID, userSwitchingReply)
             runCurrent()
+            verify(beforeUserSwitchingReply).sendResult(any())
             verify(userSwitchingReply).sendResult(any())
 
             verify(userManager).getProfiles(newID)
@@ -341,10 +344,11 @@
 
             val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
             verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
-            captor.value.onBeforeUserSwitching(newID)
+            captor.value.onBeforeUserSwitching(newID, beforeUserSwitchingReply)
             captor.value.onUserSwitching(newID, userSwitchingReply)
             runCurrent()
 
+            verify(beforeUserSwitchingReply).sendResult(any())
             verify(userSwitchingReply).sendResult(any())
             assertThat(callback.calledOnUserChanging).isEqualTo(1)
             assertThat(callback.lastUser).isEqualTo(newID)
@@ -395,7 +399,7 @@
 
             val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
             verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
-            captor.value.onBeforeUserSwitching(newID)
+            captor.value.onBeforeUserSwitching(newID, any())
             captor.value.onUserSwitchComplete(newID)
             runCurrent()
 
@@ -453,8 +457,10 @@
 
             val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
             verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+            captor.value.onBeforeUserSwitching(newID, beforeUserSwitchingReply)
             captor.value.onUserSwitching(newID, userSwitchingReply)
             runCurrent()
+            verify(beforeUserSwitchingReply).sendResult(any())
             verify(userSwitchingReply).sendResult(any())
             captor.value.onUserSwitchComplete(newID)
 
@@ -488,6 +494,7 @@
         }
 
     private class TestCallback : UserTracker.Callback {
+        var calledOnBeforeUserChanging = 0
         var calledOnUserChanging = 0
         var calledOnUserChanged = 0
         var calledOnProfilesChanged = 0
@@ -495,6 +502,11 @@
         var lastUserContext: Context? = null
         var lastUserProfiles = emptyList<UserInfo>()
 
+        override fun onBeforeUserSwitching(newUser: Int) {
+            calledOnBeforeUserChanging++
+            lastUser = newUser
+        }
+
         override fun onUserChanging(newUser: Int, userContext: Context) {
             calledOnUserChanging++
             lastUser = newUser
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 041d1a6..4b11e2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.shade
 
 import android.content.Context
-import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.annotations.RequiresFlagsDisabled
 import android.platform.test.flag.junit.FlagsParameterization
@@ -32,7 +31,6 @@
 import com.android.keyguard.KeyguardSecurityContainerController
 import com.android.keyguard.dagger.KeyguardBouncerComponent
 import com.android.systemui.Flags
-import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
@@ -406,18 +404,6 @@
         }
 
     @Test
-    @DisableSceneContainer
-    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    fun handleDispatchTouchEvent_nsslMigrationOff_userActivity_not_called() {
-        underTest.setStatusBarViewController(phoneStatusBarViewController)
-
-        interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
-
-        verify(centralSurfaces, times(0)).userActivity()
-    }
-
-    @Test
-    @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun handleDispatchTouchEvent_nsslMigrationOn_userActivity() {
         underTest.setStatusBarViewController(phoneStatusBarViewController)
 
@@ -438,7 +424,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun shouldInterceptTouchEvent_dozing_touchNotInLockIconArea_touchIntercepted() {
         // GIVEN dozing
         whenever(sysuiStatusBarStateController.isDozing).thenReturn(true)
@@ -451,7 +436,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun shouldInterceptTouchEvent_dozing_touchInStatusBar_touchIntercepted() {
         // GIVEN dozing
         whenever(sysuiStatusBarStateController.isDozing).thenReturn(true)
@@ -464,7 +448,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun shouldInterceptTouchEvent_dozingAndPulsing_touchIntercepted() {
         // GIVEN dozing
         whenever(sysuiStatusBarStateController.isDozing).thenReturn(true)
@@ -609,7 +592,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
     fun cancelCurrentTouch_callsDragDownHelper() {
         underTest.cancelCurrentTouch()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 13bc82f..a04ca03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.shade
 
-import android.platform.test.annotations.DisableFlags
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
@@ -27,7 +26,6 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.fragments.FragmentHostManager
 import com.android.systemui.fragments.FragmentService
@@ -65,10 +63,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
-/**
- * Uses Flags.KEYGUARD_STATUS_VIEW_MIGRATE_NSSL set to false. If all goes well, this set of tests
- * will be deleted.
- */
+/** NotificationsQSContainerController tests */
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
@@ -122,7 +117,7 @@
                 delayableExecutor,
                 notificationStackScrollLayoutController,
                 ResourcesSplitShadeStateController(),
-                largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
+                largeScreenHeaderHelperLazy = { largeScreenHeaderHelper },
             )
 
         overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
@@ -209,23 +204,23 @@
         given(
             taskbarVisible = true,
             navigationMode = GESTURES_NAVIGATION,
-            insets = windowInsets().withStableBottom()
+            insets = windowInsets().withStableBottom(),
         )
         then(
             expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
             expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
-            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET,
         )
 
         given(
             taskbarVisible = true,
             navigationMode = BUTTONS_NAVIGATION,
-            insets = windowInsets().withStableBottom()
+            insets = windowInsets().withStableBottom(),
         )
         then(
             expectedContainerPadding = STABLE_INSET_BOTTOM,
             expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
-            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET,
         )
     }
 
@@ -237,22 +232,22 @@
         given(
             taskbarVisible = false,
             navigationMode = GESTURES_NAVIGATION,
-            insets = windowInsets().withStableBottom()
+            insets = windowInsets().withStableBottom(),
         )
         then(
             expectedContainerPadding = 0,
-            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET,
         )
 
         given(
             taskbarVisible = false,
             navigationMode = BUTTONS_NAVIGATION,
-            insets = windowInsets().withStableBottom()
+            insets = windowInsets().withStableBottom(),
         )
         then(
             expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
             expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
-            expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+            expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET,
         )
     }
 
@@ -263,22 +258,22 @@
         given(
             taskbarVisible = false,
             navigationMode = GESTURES_NAVIGATION,
-            insets = windowInsets().withCutout()
+            insets = windowInsets().withCutout(),
         )
         then(
             expectedContainerPadding = CUTOUT_HEIGHT,
-            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET,
         )
 
         given(
             taskbarVisible = false,
             navigationMode = BUTTONS_NAVIGATION,
-            insets = windowInsets().withCutout().withStableBottom()
+            insets = windowInsets().withCutout().withStableBottom(),
         )
         then(
             expectedContainerPadding = 0,
             expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
-            expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+            expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET,
         )
     }
 
@@ -289,18 +284,18 @@
         given(
             taskbarVisible = true,
             navigationMode = GESTURES_NAVIGATION,
-            insets = windowInsets().withStableBottom()
+            insets = windowInsets().withStableBottom(),
         )
         then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
 
         given(
             taskbarVisible = true,
             navigationMode = BUTTONS_NAVIGATION,
-            insets = windowInsets().withStableBottom()
+            insets = windowInsets().withStableBottom(),
         )
         then(
             expectedContainerPadding = STABLE_INSET_BOTTOM,
-            expectedQsPadding = STABLE_INSET_BOTTOM
+            expectedQsPadding = STABLE_INSET_BOTTOM,
         )
     }
 
@@ -314,19 +309,19 @@
         given(
             taskbarVisible = false,
             navigationMode = GESTURES_NAVIGATION,
-            insets = windowInsets().withCutout().withStableBottom()
+            insets = windowInsets().withCutout().withStableBottom(),
         )
         then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
 
         given(
             taskbarVisible = false,
             navigationMode = BUTTONS_NAVIGATION,
-            insets = windowInsets().withStableBottom()
+            insets = windowInsets().withStableBottom(),
         )
         then(
             expectedContainerPadding = 0,
             expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
-            expectedQsPadding = STABLE_INSET_BOTTOM
+            expectedQsPadding = STABLE_INSET_BOTTOM,
         )
     }
 
@@ -339,7 +334,7 @@
         given(
             taskbarVisible = false,
             navigationMode = GESTURES_NAVIGATION,
-            insets = windowInsets().withStableBottom()
+            insets = windowInsets().withStableBottom(),
         )
         then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
 
@@ -355,7 +350,7 @@
         given(
             taskbarVisible = false,
             navigationMode = GESTURES_NAVIGATION,
-            insets = windowInsets().withStableBottom()
+            insets = windowInsets().withStableBottom(),
         )
         then(expectedContainerPadding = 0)
 
@@ -376,43 +371,6 @@
     }
 
     @Test
-    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    fun testSplitShadeLayout_isAlignedToGuideline() {
-        enableSplitShade()
-        underTest.updateResources()
-        assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
-        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
-            .isEqualTo(R.id.qs_edge_guideline)
-    }
-
-    @Test
-    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    fun testSinglePaneLayout_childrenHaveEqualMargins() {
-        disableSplitShade()
-        underTest.updateResources()
-        val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
-        val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin
-        val notifStartMargin = getConstraintSetLayout(R.id.notification_stack_scroller).startMargin
-        val notifEndMargin = getConstraintSetLayout(R.id.notification_stack_scroller).endMargin
-        assertThat(
-                qsStartMargin == qsEndMargin &&
-                    notifStartMargin == notifEndMargin &&
-                    qsStartMargin == notifStartMargin
-            )
-            .isTrue()
-    }
-
-    @Test
-    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
-        enableSplitShade()
-        underTest.updateResources()
-        assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
-        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin)
-            .isEqualTo(0)
-    }
-
-    @Test
     fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() {
         enableSplitShade()
         underTest.updateResources()
@@ -421,37 +379,6 @@
     }
 
     @Test
-    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeightHelper() {
-        setLargeScreen()
-        val largeScreenHeaderResourceHeight = 100
-        val largeScreenHeaderHelperHeight = 200
-        whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
-            .thenReturn(largeScreenHeaderHelperHeight)
-        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
-
-        // ensure the estimated height (would be 30 here) wouldn't impact this test case
-        overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
-        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10)
-
-        underTest.updateResources()
-
-        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
-            .isEqualTo(largeScreenHeaderHelperHeight)
-        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
-            .isEqualTo(largeScreenHeaderHelperHeight)
-    }
-
-    @Test
-    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
-        setSmallScreen()
-        underTest.updateResources()
-        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
-        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin).isEqualTo(0)
-    }
-
-    @Test
     fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() {
         disableSplitShade()
         underTest.updateResources()
@@ -464,17 +391,6 @@
     }
 
     @Test
-    @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    fun testSinglePaneShadeLayout_isAlignedToParent() {
-        disableSplitShade()
-        underTest.updateResources()
-        assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
-            .isEqualTo(ConstraintSet.PARENT_ID)
-        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
-            .isEqualTo(ConstraintSet.PARENT_ID)
-    }
-
-    @Test
     fun testAllChildrenOfNotificationContainer_haveIds() {
         // set dimen to 0 to avoid triggering updating bottom spacing
         overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0)
@@ -493,7 +409,7 @@
                 delayableExecutor,
                 notificationStackScrollLayoutController,
                 ResourcesSplitShadeStateController(),
-                largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
+                largeScreenHeaderHelperLazy = { largeScreenHeaderHelper },
             )
         controller.updateConstraints()
 
@@ -509,7 +425,7 @@
             taskbarVisible = false,
             navigationMode = GESTURES_NAVIGATION,
             insets = emptyInsets(),
-            applyImmediately = false
+            applyImmediately = false,
         )
         fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2)
         windowInsetsCallback.accept(windowInsets().withStableBottom())
@@ -576,7 +492,7 @@
         taskbarVisible: Boolean,
         navigationMode: Int,
         insets: WindowInsets,
-        applyImmediately: Boolean = true
+        applyImmediately: Boolean = true,
     ) {
         Mockito.clearInvocations(view)
         taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
@@ -591,7 +507,7 @@
     fun then(
         expectedContainerPadding: Int,
         expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
-        expectedQsPadding: Int = 0
+        expectedQsPadding: Int = 0,
     ) {
         verify(view).setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
         verify(view).setNotificationsMarginBottom(expectedNotificationsMargin)
@@ -623,7 +539,7 @@
         val layoutParams =
             ConstraintLayout.LayoutParams(
                 ViewGroup.LayoutParams.WRAP_CONTENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT
+                ViewGroup.LayoutParams.WRAP_CONTENT,
             )
         // required as cloning ConstraintSet fails if view doesn't have layout params
         view.layoutParams = layoutParams
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index a91fb45..e1a8916 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -1544,14 +1544,6 @@
         assertFalse(mStackScroller.mHeadsUpAnimatingAway);
     }
 
-    @Test
-    @EnableSceneContainer
-    public void finishExpanding_sceneContainerEnabled() {
-        mStackScroller.startOverscrollAfterExpanding();
-        verify(mStackScroller.getExpandHelper()).finishExpanding();
-        assertTrue(mStackScroller.getIsBeingDragged());
-    }
-
     private MotionEvent captureTouchSentToSceneFramework() {
         ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class);
         verify(mStackScrollLayoutController).sendTouchToSceneFramework(captor.capture());
diff --git a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
index 76fc611..25d1c37 100644
--- a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
+++ b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
@@ -433,6 +433,8 @@
 
     override fun showRearDisplayDialog(currentBaseState: Int) {}
 
+    override fun unbundleNotification(key: String) {}
+
     companion object {
         const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
         const val SECONDARY_DISPLAY_ID = 2
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index ad92b31..bfc4248 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -86,12 +86,8 @@
 }
 
 suspend fun Kosmos.setCommunalV2Enabled(enabled: Boolean) {
-    setCommunalV2ConfigEnabled(true)
-    if (enabled) {
-        fakeUserRepository.asMainUser()
-    } else {
-        fakeUserRepository.asDefaultUser()
-    }
+    setCommunalV2ConfigEnabled(enabled)
+    setCommunalEnabled(enabled)
 }
 
 suspend fun Kosmos.setCommunalAvailable(available: Boolean) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt
index b407b1b..e3cfb80 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt
@@ -17,8 +17,10 @@
 package com.android.systemui.communal.ui.viewmodel
 
 import android.service.dream.dreamManager
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.plugins.activityStarter
 import com.android.systemui.statusbar.policy.batteryController
 
 val Kosmos.communalToDreamButtonViewModel by
@@ -26,6 +28,8 @@
         CommunalToDreamButtonViewModel(
             backgroundContext = testDispatcher,
             batteryController = batteryController,
+            settingsInteractor = communalSettingsInteractor,
+            activityStarter = activityStarter,
             dreamManager = dreamManager,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
index 534ded5..9012393 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
@@ -58,4 +58,26 @@
     fun insert(instance: DisplayWindowProperties) {
         properties.put(instance.displayId, instance.windowType, instance)
     }
+
+    /** inserts an entry, mocking everything except the context. */
+    fun insertForContext(displayId: Int, windowType: Int, context: Context) {
+        properties.put(
+            displayId,
+            windowType,
+            DisplayWindowProperties(
+                displayId = displayId,
+                windowType = windowType,
+                context = context,
+                windowManager = mock(),
+                layoutInflater = mock(),
+            ),
+        )
+    }
+
+    /** Whether the repository contains an entry already. */
+    fun contains(displayId: Int, windowType: Int): Boolean =
+        properties.contains(displayId, windowType)
+
+    /** Removes all the entries. */
+    fun clear() = properties.clear()
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index d941fb0..4383560 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -17,6 +17,7 @@
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.mockito.kotlin.verify
 
 var Kosmos.testDispatcher by Fixture { StandardTestDispatcher() }
 
@@ -82,6 +83,32 @@
 }
 
 /** Retrieve the current value of this [StateFlow] safely. See `currentValue(TestScope)`. */
+fun <T> Kosmos.currentValue(fn: () -> T) = testScope.currentValue(fn)
+
+/**
+ * Retrieve the result of [fn] after running all pending tasks. Do not use to retrieve the value of
+ * a flow directly; for that, use either `currentValue(StateFlow)` or [collectLastValue]
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+fun <T> TestScope.currentValue(fn: () -> T): T {
+    runCurrent()
+    return fn()
+}
+
+/** Retrieve the result of [fn] after running all pending tasks. See `TestScope.currentValue(fn)` */
 fun <T> Kosmos.currentValue(stateFlow: StateFlow<T>): T {
     return testScope.currentValue(stateFlow)
 }
+
+/** Safely verify that a mock has been called after the test scope has caught up */
+@OptIn(ExperimentalCoroutinesApi::class)
+fun <T> TestScope.verifyCurrent(mock: T): T {
+    runCurrent()
+    return verify(mock)
+}
+
+/**
+ * Safely verify that a mock has been called after the test scope has caught up. See
+ * `TestScope.verifyCurrent`
+ */
+fun <T> Kosmos.verifyCurrent(mock: T) = testScope.verifyCurrent(mock)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 41cfcea..39f1ad4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -63,6 +63,7 @@
 import com.android.systemui.scene.domain.startable.scrimStartable
 import com.android.systemui.scene.sceneContainerConfig
 import com.android.systemui.scene.shared.model.sceneDataSource
+import com.android.systemui.scene.ui.view.mockWindowRootViewProvider
 import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -191,4 +192,5 @@
     }
     val disableFlagsInteractor by lazy { kosmos.disableFlagsInteractor }
     val fakeDisableFlagsRepository by lazy { kosmos.fakeDisableFlagsRepository }
+    val mockWindowRootViewProvider by lazy { kosmos.mockWindowRootViewProvider }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
index f52572a..f5eebb4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
@@ -19,13 +19,13 @@
 import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.TransitionKey
+import com.android.systemui.kosmos.currentValue
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.test.TestScope
 
-class FakeSceneDataSource(
-    initialSceneKey: SceneKey,
-) : SceneDataSource {
+class FakeSceneDataSource(initialSceneKey: SceneKey, val testScope: TestScope) : SceneDataSource {
 
     private val _currentScene = MutableStateFlow(initialSceneKey)
     override val currentScene: StateFlow<SceneKey> = _currentScene.asStateFlow()
@@ -33,18 +33,20 @@
     private val _currentOverlays = MutableStateFlow<Set<OverlayKey>>(emptySet())
     override val currentOverlays: StateFlow<Set<OverlayKey>> = _currentOverlays.asStateFlow()
 
-    var isPaused = false
-        private set
+    private var _isPaused = false
+    val isPaused
+        get() = testScope.currentValue { _isPaused }
 
-    var pendingScene: SceneKey? = null
-        private set
+    private var _pendingScene: SceneKey? = null
+    val pendingScene
+        get() = testScope.currentValue { _pendingScene }
 
     var pendingOverlays: Set<OverlayKey>? = null
         private set
 
     override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) {
-        if (isPaused) {
-            pendingScene = toScene
+        if (_isPaused) {
+            _pendingScene = toScene
         } else {
             _currentScene.value = toScene
         }
@@ -55,7 +57,7 @@
     }
 
     override fun showOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) {
-        if (isPaused) {
+        if (_isPaused) {
             pendingOverlays = (pendingOverlays ?: currentOverlays.value) + overlay
         } else {
             _currentOverlays.value += overlay
@@ -63,7 +65,7 @@
     }
 
     override fun hideOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) {
-        if (isPaused) {
+        if (_isPaused) {
             pendingOverlays = (pendingOverlays ?: currentOverlays.value) - overlay
         } else {
             _currentOverlays.value -= overlay
@@ -82,9 +84,9 @@
      * last one will be remembered.
      */
     fun pause() {
-        check(!isPaused) { "Can't pause what's already paused!" }
+        check(!_isPaused) { "Can't pause what's already paused!" }
 
-        isPaused = true
+        _isPaused = true
     }
 
     /**
@@ -100,15 +102,12 @@
      *
      * If [expectedScene] is provided, will assert that it's indeed the latest called.
      */
-    fun unpause(
-        force: Boolean = false,
-        expectedScene: SceneKey? = null,
-    ) {
-        check(force || isPaused) { "Can't unpause what's already not paused!" }
+    fun unpause(force: Boolean = false, expectedScene: SceneKey? = null) {
+        check(force || _isPaused) { "Can't unpause what's already not paused!" }
 
-        isPaused = false
-        pendingScene?.let { _currentScene.value = it }
-        pendingScene = null
+        _isPaused = false
+        _pendingScene?.let { _currentScene.value = it }
+        _pendingScene = null
         pendingOverlays?.let { _currentOverlays.value = it }
         pendingOverlays = null
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt
index f519686..7eebfc3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt
@@ -19,13 +19,12 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.initialSceneKey
 import com.android.systemui.scene.sceneContainerConfig
 
 val Kosmos.fakeSceneDataSource by Fixture {
-    FakeSceneDataSource(
-        initialSceneKey = initialSceneKey,
-    )
+    FakeSceneDataSource(initialSceneKey = initialSceneKey, testScope = testScope)
 }
 
 val Kosmos.sceneDataSourceDelegator by Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt
index 5c91dc8..e6ba9a5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt
@@ -17,6 +17,9 @@
 package com.android.systemui.scene.ui.view
 
 import com.android.systemui.kosmos.Kosmos
+import javax.inject.Provider
 import org.mockito.kotlin.mock
 
 val Kosmos.mockShadeRootView by Kosmos.Fixture { mock<WindowRootView>() }
+val Kosmos.mockWindowRootViewProvider by
+    Kosmos.Fixture { Provider<WindowRootView> { mock<WindowRootView>() } }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt
index 4dcd220..3ed7302 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt
@@ -16,6 +16,14 @@
 
 package com.android.systemui.shade.data.repository
 
+import android.content.applicationContext
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
 
 val Kosmos.shadeAnimationRepository by Kosmos.Fixture { ShadeAnimationRepository() }
+val Kosmos.shadeDialogContextInteractor by
+    Kosmos.Fixture {
+        mock<ShadeDialogContextInteractor> { on { context } doReturn applicationContext }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt
new file mode 100644
index 0000000..680e0de
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted
+
+import android.app.Notification
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import org.junit.Assert
+
+class FakePromotedNotificationContentExtractor : PromotedNotificationContentExtractor {
+    @JvmField
+    val contentForEntry = mutableMapOf<NotificationEntry, PromotedNotificationContentModel?>()
+    @JvmField val extractCalls = mutableListOf<Pair<NotificationEntry, Notification.Builder>>()
+
+    override fun extractContent(
+        entry: NotificationEntry,
+        recoveredBuilder: Notification.Builder,
+    ): PromotedNotificationContentModel? {
+        extractCalls.add(entry to recoveredBuilder)
+
+        if (contentForEntry.isEmpty()) {
+            // If *no* entries are set, just return null for everything.
+            return null
+        } else {
+            // If entries *are* set, fail on unexpected ones.
+            Assert.assertTrue(contentForEntry.containsKey(entry))
+            return contentForEntry.get(entry)
+        }
+    }
+
+    fun resetForEntry(entry: NotificationEntry, content: PromotedNotificationContentModel?) {
+        contentForEntry.clear()
+        contentForEntry.put(entry, content)
+        extractCalls.clear()
+    }
+
+    fun verifyZeroExtractCalls() {
+        Assert.assertTrue(extractCalls.isEmpty())
+    }
+
+    fun verifyOneExtractCall() {
+        Assert.assertEquals(1, extractCalls.size)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt
index 88caf6e..ea7b41d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt
@@ -17,11 +17,20 @@
 package com.android.systemui.statusbar.notification.promoted
 
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import org.junit.Assert
 
 class FakePromotedNotificationsProvider : PromotedNotificationsProvider {
     val promotedEntries = mutableSetOf<NotificationEntry>()
+    val shouldPromoteForEntry = mutableMapOf<NotificationEntry, Boolean>()
 
     override fun shouldPromote(entry: NotificationEntry): Boolean {
-        return promotedEntries.contains(entry)
+        if (shouldPromoteForEntry.isEmpty()) {
+            // If *no* entries are set, just return false for everything.
+            return false
+        } else {
+            // If entries *are* set, fail on unexpected ones.
+            Assert.assertTrue(shouldPromoteForEntry.containsKey(entry))
+            return shouldPromoteForEntry[entry] ?: false
+        }
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
index 5e9f12b..52c17c82f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
@@ -21,7 +21,7 @@
 
 var Kosmos.promotedNotificationContentExtractor by
     Kosmos.Fixture {
-        PromotedNotificationContentExtractor(
+        PromotedNotificationContentExtractorImpl(
             promotedNotificationsProvider,
             applicationContext,
             promotedNotificationLogger,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
index 7126933..e739e82 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
@@ -65,7 +65,7 @@
 import com.android.systemui.statusbar.notification.icon.IconBuilder
 import com.android.systemui.statusbar.notification.icon.IconManager
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractorImpl
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationLogger
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProviderImpl
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.CoordinateOnClickListener
@@ -222,16 +222,11 @@
                 Mockito.mock(LauncherApps::class.java, STUB_ONLY),
                 Mockito.mock(ConversationNotificationManager::class.java, STUB_ONLY),
             )
-
-        val promotedNotificationsProvider = PromotedNotificationsProviderImpl()
-        val promotedNotificationLog = logcatLogBuffer("PromotedNotifLog")
-        val promotedNotificationLogger = PromotedNotificationLogger(promotedNotificationLog)
-
         val promotedNotificationContentExtractor =
-            PromotedNotificationContentExtractor(
-                promotedNotificationsProvider,
+            PromotedNotificationContentExtractorImpl(
+                PromotedNotificationsProviderImpl(),
                 context,
-                promotedNotificationLogger,
+                PromotedNotificationLogger(logcatLogBuffer("PromotedNotifLog")),
             )
 
         mContentBinder =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
index 932e768..6c98d19 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.mainCoroutineContext
 import com.android.systemui.plugins.activityStarter
+import com.android.systemui.shade.data.repository.shadeDialogContextInteractor
 import com.android.systemui.statusbar.phone.systemUIDialogFactory
 import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel
 import com.android.systemui.util.mockito.mock
@@ -35,5 +36,6 @@
             { modesDialogViewModel },
             modesDialogEventLogger,
             mainCoroutineContext,
+            shadeDialogContextInteractor,
         )
     }
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 3025e2e..549f8fa 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -217,6 +217,13 @@
                                         + mPowerManagerWakeLock.getTag()));
                 return;
             }
+
+            if (!mPowerManagerWakeLock.isHeld()) {
+                Slog.w(TAG, addUserIdToLogMessage(mUserId,
+                        "Wakelock not held: " + mPowerManagerWakeLock.getTag()));
+                return;
+            }
+
             mPowerManagerWakeLock.release();
             Slog.v(
                     TAG,
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index fd18fa8..abfb826 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -55,6 +55,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.graphics.Bitmap;
+import android.media.AudioManager;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -102,6 +103,7 @@
     private final PackageManagerInternal mPackageManager;
     private final WindowManagerInternal mWmInternal;
     private final DevicePolicyManagerInternal mDpmInternal;
+    private final AudioManager mAudioManager;
     private final Object mLock = new Object();
     private final AssistDataRequester mAssistDataRequester;
 
@@ -163,6 +165,8 @@
         mAtmInternal = Objects.requireNonNull(
                 LocalServices.getService(ActivityTaskManagerInternal.class));
         mPackageManager = LocalServices.getService(PackageManagerInternal.class);
+        mAudioManager = context.getSystemService(AudioManager.class);
+
         mWmInternal = Objects.requireNonNull(LocalServices.getService(WindowManagerInternal.class));
         mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
         mAssistDataRequester = new AssistDataRequester(
@@ -306,6 +310,10 @@
                 SystemClock.uptimeMillis());
         launchIntent.putExtra(ContextualSearchManager.EXTRA_ENTRYPOINT, entrypoint);
         launchIntent.putExtra(ContextualSearchManager.EXTRA_TOKEN, mToken);
+        if (Flags.includeAudioPlayingStatus()) {
+            launchIntent.putExtra(ContextualSearchManager.EXTRA_IS_AUDIO_PLAYING,
+                    mAudioManager.isMusicActive());
+        }
         boolean isAssistDataAllowed = mAtmInternal.isAssistDataAllowed();
         final List<ActivityAssistInfo> records = mAtmInternal.getTopVisibleActivities();
         final List<IBinder> activityTokens = new ArrayList<>(records.size());
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a184e90..50b6990 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19396,9 +19396,6 @@
                     creatorPackage);
             if (creatorToken != null) {
                 extraIntent.setCreatorToken(creatorToken);
-                // TODO remove Slog.wtf once proven FrameworkStatsLog works. b/375396329
-                Slog.wtf(TAG, "A creator token is added to an intent. creatorPackage: "
-                        + creatorPackage + "; intent: " + extraIntent);
                 FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED, creatorUid, false);
             }
         });
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 87f87c7..c82933c 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -158,6 +158,7 @@
         "aoc",
         "app_widgets",
         "arc_next",
+        "art_cloud",
         "art_mainline",
         "art_performance",
         "attack_tools",
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index c31b9ef..70f2a8e 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -160,6 +160,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 /**
@@ -176,6 +177,9 @@
 class UserController implements Handler.Callback {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM;
 
+    // Amount of time we wait for observers to handle onBeforeUserSwitching, before crashing system.
+    static final int DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS = 20 * 1000;
+
     // Amount of time we wait for observers to handle a user switch before
     // giving up on them and dismissing the user switching dialog.
     static final int DEFAULT_USER_SWITCH_TIMEOUT_MS = 3 * 1000;
@@ -1920,8 +1924,14 @@
                 return false;
             }
 
-            mHandler.post(() -> startUserInternalOnHandler(userId, oldUserId, userStartMode,
-                    unlockListener, callingUid, callingPid));
+            final Runnable continueStartUserInternal = () -> continueStartUserInternal(userInfo,
+                    oldUserId, userStartMode, unlockListener, callingUid, callingPid);
+            if (foreground) {
+                mHandler.post(() -> dispatchOnBeforeUserSwitching(userId, () ->
+                        mHandler.post(continueStartUserInternal)));
+            } else {
+                continueStartUserInternal.run();
+            }
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -1929,11 +1939,11 @@
         return true;
     }
 
-    private void startUserInternalOnHandler(int userId, int oldUserId, int userStartMode,
+    private void continueStartUserInternal(UserInfo userInfo, int oldUserId, int userStartMode,
             IProgressListener unlockListener, int callingUid, int callingPid) {
         final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
         final boolean foreground = userStartMode == USER_START_MODE_FOREGROUND;
-        final UserInfo userInfo = getUserInfo(userId);
+        final int userId = userInfo.id;
 
         boolean needStart = false;
         boolean updateUmState = false;
@@ -1995,7 +2005,6 @@
             // it should be moved outside, but for now it's not as there are many calls to
             // external components here afterwards
             updateProfileRelatedCaches();
-            dispatchOnBeforeUserSwitching(userId);
             mInjector.getWindowManager().setCurrentUser(userId);
             mInjector.reportCurWakefulnessUsageEvent();
             // Once the internal notion of the active user has switched, we lock the device
@@ -2296,25 +2305,40 @@
         mUserSwitchObservers.finishBroadcast();
     }
 
-    private void dispatchOnBeforeUserSwitching(@UserIdInt int newUserId) {
+    private void dispatchOnBeforeUserSwitching(@UserIdInt int newUserId, Runnable onComplete) {
         final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
         t.traceBegin("dispatchOnBeforeUserSwitching-" + newUserId);
-        final int observerCount = mUserSwitchObservers.beginBroadcast();
-        for (int i = 0; i < observerCount; i++) {
-            final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i);
-            t.traceBegin("onBeforeUserSwitching-" + name);
+        final AtomicBoolean isSuccessful = new AtomicBoolean(false);
+        startTimeoutForOnBeforeUserSwitching(isSuccessful);
+        informUserSwitchObservers((observer, callback) -> {
             try {
-                mUserSwitchObservers.getBroadcastItem(i).onBeforeUserSwitching(newUserId);
+                observer.onBeforeUserSwitching(newUserId, callback);
             } catch (RemoteException e) {
-                // Ignore
-            } finally {
-                t.traceEnd();
+                // ignore
             }
-        }
-        mUserSwitchObservers.finishBroadcast();
+        }, () -> {
+            isSuccessful.set(true);
+            onComplete.run();
+        }, "onBeforeUserSwitching");
         t.traceEnd();
     }
 
+    private void startTimeoutForOnBeforeUserSwitching(AtomicBoolean isSuccessful) {
+        mHandler.postDelayed(() -> {
+            if (isSuccessful.get()) {
+                return;
+            }
+            String unresponsiveObservers;
+            synchronized (mLock) {
+                unresponsiveObservers = String.join(", ", mCurWaitingUserSwitchCallbacks);
+            }
+            throw new RuntimeException("Timeout on dispatchOnBeforeUserSwitching. "
+                    + "These UserSwitchObservers did not respond in "
+                    + DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS + "ms: " + unresponsiveObservers + ".");
+        }, DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS);
+    }
+
+
     /** Called on handler thread */
     @VisibleForTesting
     void dispatchUserSwitchComplete(@UserIdInt int oldUserId, @UserIdInt int newUserId) {
@@ -2527,70 +2551,76 @@
         t.traceBegin("dispatchUserSwitch-" + oldUserId + "-to-" + newUserId);
 
         EventLog.writeEvent(EventLogTags.UC_DISPATCH_USER_SWITCH, oldUserId, newUserId);
-
-        final int observerCount = mUserSwitchObservers.beginBroadcast();
-        if (observerCount > 0) {
-            final ArraySet<String> curWaitingUserSwitchCallbacks = new ArraySet<>();
-            synchronized (mLock) {
-                uss.switching = true;
-                mCurWaitingUserSwitchCallbacks = curWaitingUserSwitchCallbacks;
+        uss.switching = true;
+        informUserSwitchObservers((observer, callback) -> {
+            try {
+                observer.onUserSwitching(newUserId, callback);
+            } catch (RemoteException e) {
+                // ignore
             }
-            final AtomicInteger waitingCallbacksCount = new AtomicInteger(observerCount);
-            final long userSwitchTimeoutMs = getUserSwitchTimeoutMs();
-            final long dispatchStartedTime = SystemClock.elapsedRealtime();
-            for (int i = 0; i < observerCount; i++) {
-                final long dispatchStartedTimeForObserver = SystemClock.elapsedRealtime();
-                try {
-                    // Prepend with unique prefix to guarantee that keys are unique
-                    final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i);
-                    synchronized (mLock) {
-                        curWaitingUserSwitchCallbacks.add(name);
-                    }
-                    final IRemoteCallback callback = new IRemoteCallback.Stub() {
-                        @Override
-                        public void sendResult(Bundle data) throws RemoteException {
-                            asyncTraceEnd("onUserSwitching-" + name, newUserId);
-                            synchronized (mLock) {
-                                long delayForObserver = SystemClock.elapsedRealtime()
-                                        - dispatchStartedTimeForObserver;
-                                if (delayForObserver > LONG_USER_SWITCH_OBSERVER_WARNING_TIME_MS) {
-                                    Slogf.w(TAG, "User switch slowed down by observer " + name
-                                            + ": result took " + delayForObserver
-                                            + " ms to process.");
-                                }
-
-                                long totalDelay = SystemClock.elapsedRealtime()
-                                        - dispatchStartedTime;
-                                if (totalDelay > userSwitchTimeoutMs) {
-                                    Slogf.e(TAG, "User switch timeout: observer " + name
-                                            + "'s result was received " + totalDelay
-                                            + " ms after dispatchUserSwitch.");
-                                }
-
-                                curWaitingUserSwitchCallbacks.remove(name);
-                                // Continue switching if all callbacks have been notified and
-                                // user switching session is still valid
-                                if (waitingCallbacksCount.decrementAndGet() == 0
-                                        && (curWaitingUserSwitchCallbacks
-                                        == mCurWaitingUserSwitchCallbacks)) {
-                                    sendContinueUserSwitchLU(uss, oldUserId, newUserId);
-                                }
-                            }
-                        }
-                    };
-                    asyncTraceBegin("onUserSwitching-" + name, newUserId);
-                    mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(newUserId, callback);
-                } catch (RemoteException e) {
-                    // Ignore
-                }
-            }
-        } else {
+        }, () -> {
             synchronized (mLock) {
                 sendContinueUserSwitchLU(uss, oldUserId, newUserId);
             }
+        }, "onUserSwitching");
+        t.traceEnd();
+    }
+
+    void informUserSwitchObservers(BiConsumer<IUserSwitchObserver, IRemoteCallback> consumer,
+            final Runnable onComplete, String trace) {
+        final int observerCount = mUserSwitchObservers.beginBroadcast();
+        if (observerCount == 0) {
+            onComplete.run();
+            mUserSwitchObservers.finishBroadcast();
+            return;
+        }
+        final ArraySet<String> curWaitingUserSwitchCallbacks = new ArraySet<>();
+        synchronized (mLock) {
+            mCurWaitingUserSwitchCallbacks = curWaitingUserSwitchCallbacks;
+        }
+        final AtomicInteger waitingCallbacksCount = new AtomicInteger(observerCount);
+        final long userSwitchTimeoutMs = getUserSwitchTimeoutMs();
+        final long dispatchStartedTime = SystemClock.elapsedRealtime();
+        for (int i = 0; i < observerCount; i++) {
+            final long dispatchStartedTimeForObserver = SystemClock.elapsedRealtime();
+            // Prepend with unique prefix to guarantee that keys are unique
+            final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i);
+            synchronized (mLock) {
+                curWaitingUserSwitchCallbacks.add(name);
+            }
+            final IRemoteCallback callback = new IRemoteCallback.Stub() {
+                @Override
+                public void sendResult(Bundle data) throws RemoteException {
+                    asyncTraceEnd(trace + "-" + name, 0);
+                    synchronized (mLock) {
+                        long delayForObserver = SystemClock.elapsedRealtime()
+                                - dispatchStartedTimeForObserver;
+                        if (delayForObserver > LONG_USER_SWITCH_OBSERVER_WARNING_TIME_MS) {
+                            Slogf.w(TAG, "User switch slowed down by observer " + name
+                                    + ": result took " + delayForObserver
+                                    + " ms to process. " + trace);
+                        }
+                        long totalDelay = SystemClock.elapsedRealtime() - dispatchStartedTime;
+                        if (totalDelay > userSwitchTimeoutMs) {
+                            Slogf.e(TAG, "User switch timeout: observer " + name
+                                    + "'s result was received " + totalDelay
+                                    + " ms after dispatchUserSwitch. " + trace);
+                        }
+                        curWaitingUserSwitchCallbacks.remove(name);
+                        // Continue switching if all callbacks have been notified and
+                        // user switching session is still valid
+                        if (waitingCallbacksCount.decrementAndGet() == 0
+                                && (curWaitingUserSwitchCallbacks
+                                == mCurWaitingUserSwitchCallbacks)) {
+                            onComplete.run();
+                        }
+                    }
+                }
+            };
+            asyncTraceBegin(trace + "-" + name, 0);
+            consumer.accept(mUserSwitchObservers.getBroadcastItem(i), callback);
         }
         mUserSwitchObservers.finishBroadcast();
-        t.traceEnd(); // end dispatchUserSwitch-
     }
 
     @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index bad5b8b..5740e16 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2397,9 +2397,13 @@
         // We don't bother invalidating the display info caches here because any changes to the
         // display info will trigger a cache invalidation inside of LogicalDisplay before we hit
         // this point.
-        sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+        sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
 
         applyDisplayChangedLocked(display);
+
+        if (mDisplayTopologyCoordinator != null) {
+            mDisplayTopologyCoordinator.onDisplayChanged(display.getDisplayInfoLocked());
+        }
     }
 
     private void applyDisplayChangedLocked(@NonNull LogicalDisplay display) {
@@ -2643,7 +2647,8 @@
 
     private void updateCanHostTasksIfNeededLocked(LogicalDisplay display) {
         if (display.setCanHostTasksLocked(!mMirrorBuiltInDisplay)) {
-            sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+            sendDisplayEventIfEnabledLocked(display,
+                    DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
         }
     }
 
@@ -3474,7 +3479,7 @@
 
     private void sendDisplayEventFrameRateOverrideLocked(int displayId) {
         Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE,
-                displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+                displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
         mHandler.sendMessage(msg);
     }
 
@@ -4061,7 +4066,7 @@
                     handleLogicalDisplayAddedLocked(display);
                     break;
 
-                case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CHANGED:
+                case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_BASIC_CHANGED:
                     handleLogicalDisplayChangedLocked(display);
                     break;
 
@@ -4286,8 +4291,9 @@
             switch (event) {
                 case DisplayManagerGlobal.EVENT_DISPLAY_ADDED:
                     return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED) != 0;
-                case DisplayManagerGlobal.EVENT_DISPLAY_CHANGED:
-                    return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED) != 0;
+                case DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED:
+                    return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED)
+                            != 0;
                 case DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED:
                     return (mask
                             & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED)
@@ -4542,7 +4548,8 @@
         public void registerCallback(IDisplayManagerCallback callback) {
             registerCallbackWithEventMask(callback,
                     DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
-                    | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+                    | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+                    | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE
                     | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED);
         }
 
diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
index 5b78726..461a9f3 100644
--- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
+++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
@@ -85,13 +85,26 @@
     }
 
     /**
+     * Update the topology with display changes.
+     * @param info The new display info
+     */
+    void onDisplayChanged(DisplayInfo info) {
+        synchronized (mSyncRoot) {
+            if (mTopology.updateDisplay(info.displayId, getWidth(info), getHeight(info))) {
+                sendTopologyUpdateLocked();
+            }
+        }
+    }
+
+    /**
      * Remove a display from the topology.
      * @param displayId The logical display ID
      */
     void onDisplayRemoved(int displayId) {
         synchronized (mSyncRoot) {
-            mTopology.removeDisplay(displayId);
-            sendTopologyUpdateLocked();
+            if (mTopology.removeDisplay(displayId)) {
+                sendTopologyUpdateLocked();
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 79592a65..0069215 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -81,7 +81,7 @@
 
     public static final int LOGICAL_DISPLAY_EVENT_BASE = 0;
     public static final int LOGICAL_DISPLAY_EVENT_ADDED = 1 << 0;
-    public static final int LOGICAL_DISPLAY_EVENT_CHANGED = 1 << 1;
+    public static final int LOGICAL_DISPLAY_EVENT_BASIC_CHANGED = 1 << 1;
     public static final int LOGICAL_DISPLAY_EVENT_REMOVED = 1 << 2;
     public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 1 << 3;
     public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 1 << 4;
@@ -172,9 +172,7 @@
 
     /**
      * Has an entry for every logical display that the rest of the system has been notified about.
-     * Any entry in here requires us to send a {@link  LOGICAL_DISPLAY_EVENT_REMOVED} event when it
-     * is deleted or {@link  LOGICAL_DISPLAY_EVENT_CHANGED} when it is changed. The values are any
-     * of the {@code UPDATE_STATE_*} constant types.
+     * The values are any of the {@code UPDATE_STATE_*} constant types.
      */
     private final SparseIntArray mUpdatedLogicalDisplays = new SparseIntArray();
 
@@ -811,7 +809,8 @@
             final boolean isCurrentlyEnabled = display.isEnabledLocked();
             int logicalDisplayEventMask = mLogicalDisplaysToUpdate
                     .get(displayId, LOGICAL_DISPLAY_EVENT_BASE);
-
+            boolean hasBasicInfoChanged =
+                    !mTempDisplayInfo.equals(newDisplayInfo, /* compareRefreshRate */ false);
             // The display is no longer valid and needs to be removed.
             if (!display.isValidLocked()) {
                 // Remove from group
@@ -863,19 +862,28 @@
                 int event = isCurrentlyEnabled ? LOGICAL_DISPLAY_EVENT_ADDED :
                         LOGICAL_DISPLAY_EVENT_REMOVED;
                 logicalDisplayEventMask |= event;
-            } else if (wasDirty || !mTempDisplayInfo.equals(newDisplayInfo)) {
+            } else if (wasDirty) {
                 // If only the hdr/sdr ratio changed, then send just the event for that case
                 if ((diff == DisplayDeviceInfo.DIFF_HDR_SDR_RATIO)) {
                     logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED;
                 } else {
-                    logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_CHANGED;
+                    logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED
+                            | LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED
+                            | LOGICAL_DISPLAY_EVENT_STATE_CHANGED;
                 }
+            } else if (hasBasicInfoChanged
+                    || mTempDisplayInfo.getRefreshRate() != newDisplayInfo.getRefreshRate()) {
+                // If only the hdr/sdr ratio changed, then send just the event for that case
+                if ((diff == DisplayDeviceInfo.DIFF_HDR_SDR_RATIO)) {
+                    logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED;
+                } else {
 
-                if (mFlags.isDisplayListenerPerformanceImprovementsEnabled()) {
+                    if (hasBasicInfoChanged) {
+                        logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED;
+                    }
                     logicalDisplayEventMask
                             |= updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo);
                 }
-
                 // The display is involved in a display layout transition
             } else if (updateState == UPDATE_STATE_TRANSITION) {
                 logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION;
@@ -891,7 +899,8 @@
                 // things like display cutouts.
                 display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo);
                 if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) {
-                    logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_CHANGED;
+                    logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED
+                            | LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED;
                 }
             }
             mLogicalDisplaysToUpdate.put(displayId, logicalDisplayEventMask);
@@ -930,7 +939,7 @@
         if (mFlags.isConnectedDisplayManagementEnabled()) {
             sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_DISCONNECTED);
         }
-        sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CHANGED);
+        sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_BASIC_CHANGED);
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED);
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_STATE_CHANGED);
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
@@ -962,7 +971,8 @@
             mask |= LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED;
         }
 
-        if (mTempDisplayInfo.state != newDisplayInfo.state) {
+        if (mFlags.isDisplayListenerPerformanceImprovementsEnabled()
+                && mTempDisplayInfo.state != newDisplayInfo.state) {
             mask |= LOGICAL_DISPLAY_EVENT_STATE_CHANGED;
         }
         return mask;
@@ -1357,8 +1367,6 @@
                 return "added";
             case LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION:
                 return "transition";
-            case LOGICAL_DISPLAY_EVENT_CHANGED:
-                return "changed";
             case LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED:
                 return "framerate_override";
             case LOGICAL_DISPLAY_EVENT_SWAPPED:
@@ -1375,6 +1383,8 @@
                 return "state_changed";
             case LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED:
                 return "refresh_rate_changed";
+            case LOGICAL_DISPLAY_EVENT_BASIC_CHANGED:
+                return "basic_changed";
         }
         return null;
     }
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 58c8450..0d6e502 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -21,6 +21,7 @@
 import android.content.ComponentName;
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
+import android.media.MediaRoute2ProviderService.Reason;
 import android.media.MediaRouter2;
 import android.media.MediaRouter2Utils;
 import android.media.RouteDiscoveryPreference;
@@ -123,6 +124,13 @@
         }
     }
 
+    /** Calls {@link Callback#onRequestFailed} with the given id and reason. */
+    protected void notifyRequestFailed(long requestId, @Reason int reason) {
+        if (mCallback != null) {
+            mCallback.onRequestFailed(/* provider= */ this, requestId, reason);
+        }
+    }
+
     void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo) {
         setProviderState(providerInfo);
         notifyProviderState();
@@ -171,11 +179,34 @@
         void onProviderStateChanged(@Nullable MediaRoute2Provider provider);
         void onSessionCreated(@NonNull MediaRoute2Provider provider,
                 long requestId, @Nullable RoutingSessionInfo sessionInfo);
-        void onSessionUpdated(@NonNull MediaRoute2Provider provider,
-                @NonNull RoutingSessionInfo sessionInfo);
+
+        /**
+         * Called when there's a session info change.
+         *
+         * <p>If the provided {@code sessionInfo} has a null {@link
+         * RoutingSessionInfo#getClientPackageName()}, that means that it's applicable to all
+         * packages. We call this type of routing session "global". This is typically used for
+         * system provided {@link RoutingSessionInfo}. However, some applications may be exempted
+         * from the global routing sessions, because their media is being routed using a session
+         * different from the global routing session.
+         *
+         * @param provider The provider that owns the session that changed.
+         * @param sessionInfo The new {@link RoutingSessionInfo}.
+         * @param packageNamesWithRoutingSessionOverrides The names of packages that are not
+         *     affected by global session changes. This set may only be non-empty when the {@code
+         *     sessionInfo} is for the global session, and therefore has no {@link
+         *     RoutingSessionInfo#getClientPackageName()}.
+         */
+        void onSessionUpdated(
+                @NonNull MediaRoute2Provider provider,
+                @NonNull RoutingSessionInfo sessionInfo,
+                Set<String> packageNamesWithRoutingSessionOverrides);
+
         void onSessionReleased(@NonNull MediaRoute2Provider provider,
                 @NonNull RoutingSessionInfo sessionInfo);
-        void onRequestFailed(@NonNull MediaRoute2Provider provider, long requestId, int reason);
+
+        void onRequestFailed(
+                @NonNull MediaRoute2Provider provider, long requestId, @Reason int reason);
     }
 
     /**
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index f09be2c..d6f7d3b 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -16,6 +16,7 @@
 
 package com.android.server.media;
 
+import static android.media.MediaRoute2ProviderService.REASON_REJECTED;
 import static android.media.MediaRoute2ProviderService.REQUEST_ID_NONE;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -31,6 +32,7 @@
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2ProviderService;
+import android.media.MediaRoute2ProviderService.Reason;
 import android.media.RouteDiscoveryPreference;
 import android.media.RoutingSessionInfo;
 import android.os.Bundle;
@@ -41,6 +43,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.Slog;
@@ -89,6 +92,12 @@
             mRequestIdToSessionCreationRequest;
 
     @GuardedBy("mLock")
+    private final Map<String, SystemMediaSessionCallback> mSystemSessionCallbacks;
+
+    @GuardedBy("mLock")
+    private final LongSparseArray<SystemMediaSessionCallback> mRequestIdToSystemSessionRequest;
+
+    @GuardedBy("mLock")
     private final Map<String, SessionCreationOrTransferRequest> mSessionOriginalIdToTransferRequest;
 
     MediaRoute2ProviderServiceProxy(
@@ -102,6 +111,8 @@
         mContext = Objects.requireNonNull(context, "Context must not be null.");
         mRequestIdToSessionCreationRequest = new LongSparseArray<>();
         mSessionOriginalIdToTransferRequest = new HashMap<>();
+        mRequestIdToSystemSessionRequest = new LongSparseArray<>();
+        mSystemSessionCallbacks = new ArrayMap<>();
         mIsSelfScanOnlyProvider = isSelfScanOnlyProvider;
         mSupportsSystemMediaRouting = supportsSystemMediaRouting;
         mUserId = userId;
@@ -236,6 +247,48 @@
         }
     }
 
+    /**
+     * Requests the creation of a system media routing session.
+     *
+     * @param requestId The id of the request.
+     * @param uid The uid of the package whose media to route, or {@link
+     *     android.os.Process#INVALID_UID} if not applicable (for example, if all the system's media
+     *     must be routed).
+     * @param packageName The package name to populate {@link
+     *     RoutingSessionInfo#getClientPackageName()}.
+     * @param routeId The id of the route to be initially {@link
+     *     RoutingSessionInfo#getSelectedRoutes()}.
+     * @param sessionHints An optional bundle with paramets.
+     * @param callback A {@link SystemMediaSessionCallback} to notify of session events.
+     * @see MediaRoute2ProviderService#onCreateSystemRoutingSession
+     */
+    public void requestCreateSystemMediaSession(
+            long requestId,
+            int uid,
+            String packageName,
+            String routeId,
+            @Nullable Bundle sessionHints,
+            @NonNull SystemMediaSessionCallback callback) {
+        if (!Flags.enableMirroringInMediaRouter2()) {
+            throw new IllegalStateException(
+                    "Unexpected call to requestCreateSystemMediaSession. Governing flag is"
+                            + " disabled.");
+        }
+        if (mConnectionReady) {
+            boolean binderRequestSucceeded =
+                    mActiveConnection.requestCreateSystemMediaSession(
+                            requestId, uid, packageName, routeId, sessionHints);
+            if (!binderRequestSucceeded) {
+                // notify failure.
+                return;
+            }
+            updateBinding();
+            synchronized (mLock) {
+                mRequestIdToSystemSessionRequest.put(requestId, callback);
+            }
+        }
+    }
+
     public boolean hasComponentName(String packageName, String className) {
         return mComponentName.getPackageName().equals(packageName)
                 && mComponentName.getClassName().equals(className);
@@ -292,7 +345,14 @@
                 mLastDiscoveryPreference != null
                         && mLastDiscoveryPreference.shouldPerformActiveScan()
                         && mSupportsSystemMediaRouting;
+        boolean bindDueToOngoingSystemMediaRoutingSessions = false;
+        if (Flags.enableMirroringInMediaRouter2()) {
+            synchronized (mLock) {
+                bindDueToOngoingSystemMediaRoutingSessions = !mSystemSessionCallbacks.isEmpty();
+            }
+        }
         if (!getSessionInfos().isEmpty()
+                || bindDueToOngoingSystemMediaRoutingSessions
                 || bindDueToManagerScan
                 || bindDueToSystemMediaRoutingSupport) {
             return true;
@@ -438,6 +498,14 @@
         String newSessionId = newSession.getId();
 
         synchronized (mLock) {
+            var systemMediaSessionCallback = mRequestIdToSystemSessionRequest.get(requestId);
+            if (systemMediaSessionCallback != null) {
+                mRequestIdToSystemSessionRequest.remove(requestId);
+                mSystemSessionCallbacks.put(newSession.getOriginalId(), systemMediaSessionCallback);
+                systemMediaSessionCallback.onSessionUpdate(newSession);
+                return;
+            }
+
             if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
                 newSession =
                         createSessionWithPopulatedTransferInitiationDataLocked(
@@ -569,6 +637,12 @@
 
         boolean found = false;
         synchronized (mLock) {
+            var sessionCallback = mSystemSessionCallbacks.get(releasedSession.getOriginalId());
+            if (sessionCallback != null) {
+                sessionCallback.onSessionReleased();
+                return;
+            }
+
             mSessionOriginalIdToTransferRequest.remove(releasedSession.getId());
             for (RoutingSessionInfo session : mSessionInfos) {
                 if (TextUtils.equals(session.getId(), releasedSession.getId())) {
@@ -602,7 +676,11 @@
 
     private void dispatchSessionUpdated(RoutingSessionInfo session) {
         mHandler.sendMessage(
-                obtainMessage(mCallback::onSessionUpdated, this, session));
+                obtainMessage(
+                        mCallback::onSessionUpdated,
+                        this,
+                        session,
+                        /* packageNamesWithRoutingSessionOverrides= */ Set.of()));
     }
 
     private void dispatchSessionReleased(RoutingSessionInfo session) {
@@ -645,6 +723,19 @@
                 for (RoutingSessionInfo sessionInfo : mSessionInfos) {
                     mCallback.onSessionReleased(this, sessionInfo);
                 }
+                if (Flags.enableMirroringInMediaRouter2()) {
+                    for (var callback : mSystemSessionCallbacks.values()) {
+                        callback.onSessionReleased();
+                    }
+                    mSystemSessionCallbacks.clear();
+                    int requestsSize = mRequestIdToSystemSessionRequest.size();
+                    for (int i = 0; i < requestsSize; i++) {
+                        var callback = mRequestIdToSystemSessionRequest.valueAt(i);
+                        var requestId = mRequestIdToSystemSessionRequest.keyAt(i);
+                        callback.onRequestFailed(requestId, REASON_REJECTED);
+                    }
+                    mSystemSessionCallbacks.clear();
+                }
                 mSessionInfos.clear();
                 mReleasingSessions.clear();
                 mRequestIdToSessionCreationRequest.clear();
@@ -673,6 +764,26 @@
                 pendingTransferCount);
     }
 
+    /**
+     * Callback for events related to system media sessions.
+     *
+     * @see MediaRoute2ProviderService#onCreateSystemRoutingSession
+     */
+    public interface SystemMediaSessionCallback {
+
+        /**
+         * Called when the corresponding session's {@link RoutingSessionInfo}, or upon the creation
+         * of the given session info.
+         */
+        void onSessionUpdate(@NonNull RoutingSessionInfo sessionInfo);
+
+        /** Called when the request with the given id fails for the given reason. */
+        void onRequestFailed(long requestId, @Reason int reason);
+
+        /** Called when the corresponding session is released. */
+        void onSessionReleased();
+    }
+
     // All methods in this class are called on the main thread.
     private final class ServiceConnectionImpl implements ServiceConnection {
 
@@ -739,6 +850,28 @@
             }
         }
 
+        /**
+         * Sends a system media session creation request to the provider service, and returns
+         * whether the request transaction succeeded.
+         *
+         * <p>The transaction might fail, for example, if the recipient process has died.
+         */
+        public boolean requestCreateSystemMediaSession(
+                long requestId,
+                int uid,
+                String packageName,
+                String routeId,
+                @Nullable Bundle sessionHints) {
+            try {
+                mService.requestCreateSystemMediaSession(
+                        requestId, uid, packageName, routeId, sessionHints);
+                return true;
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "requestCreateSystemMediaSession: Failed to deliver request.");
+            }
+            return false;
+        }
+
         public void releaseSession(long requestId, String sessionId) {
             try {
                 mService.releaseSession(requestId, sessionId);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 58deffc..5e6737a4 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -846,33 +846,29 @@
         try {
             synchronized (mLock) {
                 UserRecord userRecord = getOrCreateUserRecordLocked(userId);
-                List<RoutingSessionInfo> sessionInfos;
+                SystemMediaRoute2Provider systemProvider = userRecord.mHandler.getSystemProvider();
                 if (hasSystemRoutingPermissions) {
-                    if (setDeviceRouteSelected && !Flags.enableMirroringInMediaRouter2()) {
+                    if (!Flags.enableMirroringInMediaRouter2() && setDeviceRouteSelected) {
                         // Return a fake system session that shows the device route as selected and
                         // available bluetooth routes as transferable.
-                        return userRecord.mHandler.getSystemProvider()
-                                .generateDeviceRouteSelectedSessionInfo(targetPackageName);
+                        return systemProvider.generateDeviceRouteSelectedSessionInfo(
+                                targetPackageName);
                     } else {
-                        sessionInfos = userRecord.mHandler.getSystemProvider().getSessionInfos();
-                        if (!sessionInfos.isEmpty()) {
-                            // Return a copy of the current system session with no modification,
-                            // except setting the client package name.
-                            return new RoutingSessionInfo.Builder(sessionInfos.get(0))
-                                    .setClientPackageName(targetPackageName)
-                                    .build();
+                        RoutingSessionInfo session =
+                                systemProvider.getSessionForPackage(targetPackageName);
+                        if (session != null) {
+                            return session;
                         } else {
                             Slog.w(TAG, "System provider does not have any session info.");
+                            return null;
                         }
                     }
                 } else {
-                    return new RoutingSessionInfo.Builder(
-                                    userRecord.mHandler.getSystemProvider().getDefaultSessionInfo())
+                    return new RoutingSessionInfo.Builder(systemProvider.getDefaultSessionInfo())
                             .setClientPackageName(targetPackageName)
                             .build();
                 }
             }
-            return null;
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -2638,10 +2634,17 @@
         }
 
         @Override
-        public void onSessionUpdated(@NonNull MediaRoute2Provider provider,
-                @NonNull RoutingSessionInfo sessionInfo) {
-            sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionInfoChangedOnHandler,
-                    this, provider, sessionInfo));
+        public void onSessionUpdated(
+                @NonNull MediaRoute2Provider provider,
+                @NonNull RoutingSessionInfo sessionInfo,
+                Set<String> packageNamesWithRoutingSessionOverrides) {
+            sendMessage(
+                    PooledLambda.obtainMessage(
+                            UserHandler::onSessionInfoChangedOnHandler,
+                            this,
+                            provider,
+                            sessionInfo,
+                            packageNamesWithRoutingSessionOverrides));
         }
 
         @Override
@@ -3152,10 +3155,31 @@
                     toOriginalRequestId(uniqueRequestId), sessionInfo);
         }
 
-        private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
-                @NonNull RoutingSessionInfo sessionInfo) {
+        /**
+         * Implementation of {@link MediaRoute2Provider.Callback#onSessionUpdated}.
+         *
+         * <p>Must run on the thread that corresponds to this {@link UserHandler}.
+         */
+        private void onSessionInfoChangedOnHandler(
+                @NonNull MediaRoute2Provider provider,
+                @NonNull RoutingSessionInfo sessionInfo,
+                Set<String> packageNamesWithRoutingSessionOverrides) {
             List<ManagerRecord> managers = getManagerRecords();
             for (ManagerRecord manager : managers) {
+                if (Flags.enableMirroringInMediaRouter2()) {
+                    String targetPackageName = manager.mTargetPackageName;
+                    boolean skipDueToOverride =
+                            targetPackageName != null
+                                    && packageNamesWithRoutingSessionOverrides.contains(
+                                            targetPackageName);
+                    boolean sessionIsForTargetPackage =
+                            TextUtils.isEmpty(sessionInfo.getClientPackageName()) // is global.
+                                    || TextUtils.equals(
+                                            targetPackageName, sessionInfo.getClientPackageName());
+                    if (skipDueToOverride || !sessionIsForTargetPackage) {
+                        continue;
+                    }
+                }
                 manager.notifySessionUpdated(sessionInfo);
             }
 
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index b93846b..60fced1 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -62,7 +62,7 @@
     static final String SYSTEM_SESSION_ID = "SYSTEM_SESSION";
 
     private final AudioManager mAudioManager;
-    private final Handler mHandler;
+    protected final Handler mHandler;
     private final Context mContext;
     private final UserHandle mUser;
 
@@ -116,7 +116,7 @@
                         () -> {
                             publishProviderState();
                             if (updateSessionInfosIfNeeded()) {
-                                notifySessionInfoUpdated();
+                                notifyGlobalSessionInfoUpdated();
                             }
                         });
 
@@ -129,7 +129,7 @@
                                         () -> {
                                             publishProviderState();
                                             if (updateSessionInfosIfNeeded()) {
-                                                notifySessionInfoUpdated();
+                                                notifyGlobalSessionInfoUpdated();
                                             }
                                         }));
     }
@@ -161,7 +161,7 @@
     public void setCallback(Callback callback) {
         super.setCallback(callback);
         notifyProviderState();
-        notifySessionInfoUpdated();
+        notifyGlobalSessionInfoUpdated();
     }
 
     @Override
@@ -296,7 +296,7 @@
 
         if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()
                 && updateSessionInfosIfNeeded()) {
-            notifySessionInfoUpdated();
+            notifyGlobalSessionInfoUpdated();
         }
     }
 
@@ -327,6 +327,23 @@
     }
 
     /**
+     * Returns the {@link RoutingSessionInfo} that corresponds to the package with the given name.
+     */
+    public RoutingSessionInfo getSessionForPackage(String targetPackageName) {
+        synchronized (mLock) {
+            if (!mSessionInfos.isEmpty()) {
+                // Return a copy of the current system session with no modification,
+                // except setting the client package name.
+                return new RoutingSessionInfo.Builder(mSessionInfos.get(0))
+                        .setClientPackageName(targetPackageName)
+                        .build();
+            } else {
+                return null;
+            }
+        }
+    }
+
+    /**
      * Builds a system {@link RoutingSessionInfo} with the selected route set to the currently
      * selected <b>device</b> route (wired or built-in, but not bluetooth) and transferable routes
      * set to the currently available (connected) bluetooth routes.
@@ -626,20 +643,21 @@
         notifyProviderState();
     }
 
-    void notifySessionInfoUpdated() {
+    void notifyGlobalSessionInfoUpdated() {
         if (mCallback == null) {
             return;
         }
 
         RoutingSessionInfo sessionInfo;
         synchronized (mLock) {
-            sessionInfo = mSessionInfos.get(0);
-            if (sessionInfo == null) {
+            if (mSessionInfos.isEmpty()) {
                 return;
             }
+            sessionInfo = mSessionInfos.get(0);
         }
 
-        mCallback.onSessionUpdated(this, sessionInfo);
+        mCallback.onSessionUpdated(
+                this, sessionInfo, /* packageNamesWithRoutingSessionOverrides= */ Set.of());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
index 7dc30ab..8931e3a 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
@@ -18,23 +18,33 @@
 
 import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2ProviderService;
+import android.media.MediaRoute2ProviderService.Reason;
+import android.media.MediaRouter2Utils;
 import android.media.RoutingSessionInfo;
+import android.os.Binder;
 import android.os.Looper;
+import android.os.Process;
 import android.os.UserHandle;
-import android.util.ArraySet;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.LongSparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.server.media.MediaRoute2ProviderServiceProxy.SystemMediaSessionCallback;
 
-import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Stream;
 
 /**
@@ -48,11 +58,33 @@
     private static final String ROUTE_ID_PREFIX_SYSTEM = "SYSTEM";
     private static final String ROUTE_ID_SYSTEM_SEPARATOR = ".";
 
+    private final PackageManager mPackageManager;
+
     @GuardedBy("mLock")
     private MediaRoute2ProviderInfo mLastSystemProviderInfo;
 
     @GuardedBy("mLock")
-    private final Map<String, ProviderProxyRecord> mProxyRecords = new HashMap<>();
+    private final Map<String, ProviderProxyRecord> mProxyRecords = new ArrayMap<>();
+
+    /**
+     * Maps package names to corresponding sessions maintained by {@link MediaRoute2ProviderService
+     * provider services}.
+     */
+    @GuardedBy("mLock")
+    private final Map<String, SystemMediaSessionRecord> mPackageNameToSessionRecord =
+            new ArrayMap<>();
+
+    /**
+     * Maps route {@link MediaRoute2Info#getOriginalId original ids} to the id of the {@link
+     * MediaRoute2ProviderService provider service} that manages the corresponding route.
+     */
+    @GuardedBy("mLock")
+    private final Map<String, String> mOriginalRouteIdToProviderId = new ArrayMap<>();
+
+    /** Maps request ids to pending session creation callbacks. */
+    @GuardedBy("mLock")
+    private final LongSparseArray<SystemMediaSessionCallbackImpl> mPendingSessionCreations =
+            new LongSparseArray<>();
 
     private static final ComponentName COMPONENT_NAME =
             new ComponentName(
@@ -69,6 +101,128 @@
 
     private SystemMediaRoute2Provider2(Context context, UserHandle user, Looper looper) {
         super(context, COMPONENT_NAME, user, looper);
+        mPackageManager = context.getPackageManager();
+    }
+
+    @Override
+    public void transferToRoute(
+            long requestId,
+            @NonNull UserHandle clientUserHandle,
+            @NonNull String clientPackageName,
+            String sessionOriginalId,
+            String routeOriginalId,
+            int transferReason) {
+        synchronized (mLock) {
+            var targetProviderProxyId = mOriginalRouteIdToProviderId.get(routeOriginalId);
+            var targetProviderProxyRecord = mProxyRecords.get(targetProviderProxyId);
+            // Holds the target route, if it's managed by a provider service. Holds null otherwise.
+            var serviceTargetRoute =
+                    targetProviderProxyRecord != null
+                            ? targetProviderProxyRecord.getRouteByOriginalId(routeOriginalId)
+                            : null;
+            var existingSessionRecord = mPackageNameToSessionRecord.get(clientPackageName);
+            if (existingSessionRecord != null) {
+                var existingSession = existingSessionRecord.mSourceSessionInfo;
+                if (targetProviderProxyId != null
+                        && TextUtils.equals(
+                                targetProviderProxyId, existingSession.getProviderId())) {
+                    // The currently selected route and target route both belong to the same
+                    // provider. We tell the provider to handle the transfer.
+                    targetProviderProxyRecord.requestTransfer(
+                            existingSession.getOriginalId(), serviceTargetRoute);
+                } else {
+                    // The target route is handled by a provider other than the target one. We need
+                    // to release the existing session.
+                    var currentProxyRecord = existingSessionRecord.getProxyRecord();
+                    if (currentProxyRecord != null) {
+                        currentProxyRecord.releaseSession(
+                                requestId, existingSession.getOriginalId());
+                        existingSessionRecord.removeSelfFromSessionMap();
+                    }
+                }
+            }
+
+            if (serviceTargetRoute != null) {
+                boolean isGlobalSession = TextUtils.isEmpty(clientPackageName);
+                int uid;
+                if (isGlobalSession) {
+                    uid = Process.INVALID_UID;
+                } else {
+                    uid = fetchUid(clientPackageName, clientUserHandle);
+                    if (uid == Process.INVALID_UID) {
+                        throw new IllegalArgumentException(
+                                "Cannot resolve transfer for "
+                                        + clientPackageName
+                                        + " and "
+                                        + clientUserHandle);
+                    }
+                }
+                var pendingCreationCallback =
+                        new SystemMediaSessionCallbackImpl(
+                                targetProviderProxyId, requestId, clientPackageName);
+                mPendingSessionCreations.put(requestId, pendingCreationCallback);
+                targetProviderProxyRecord.requestCreateSystemMediaSession(
+                        requestId,
+                        uid,
+                        clientPackageName,
+                        routeOriginalId,
+                        pendingCreationCallback);
+            } else {
+                // The target route is not provided by any of the services. Assume it's a system
+                // provided route.
+                super.transferToRoute(
+                        requestId,
+                        clientUserHandle,
+                        clientPackageName,
+                        sessionOriginalId,
+                        routeOriginalId,
+                        transferReason);
+            }
+        }
+    }
+
+    @Nullable
+    @Override
+    public RoutingSessionInfo getSessionForPackage(String packageName) {
+        synchronized (mLock) {
+            var systemSession = super.getSessionForPackage(packageName);
+            if (systemSession == null) {
+                return null;
+            }
+            var overridingSession = mPackageNameToSessionRecord.get(packageName);
+            if (overridingSession != null) {
+                var builder =
+                        new RoutingSessionInfo.Builder(overridingSession.mTranslatedSessionInfo)
+                                .setProviderId(mUniqueId)
+                                .setSystemSession(true);
+                for (var systemRoute : mLastSystemProviderInfo.getRoutes()) {
+                    builder.addTransferableRoute(systemRoute.getOriginalId());
+                }
+                return builder.build();
+            } else {
+                return systemSession;
+            }
+        }
+    }
+
+    /**
+     * Returns the uid that corresponds to the given name and user handle, or {@link
+     * Process#INVALID_UID} if a uid couldn't be found.
+     */
+    @SuppressLint("MissingPermission")
+    // We clear the calling identity before calling the package manager, and we are running on the
+    // system_server.
+    private int fetchUid(String clientPackageName, UserHandle clientUserHandle) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return mPackageManager.getApplicationInfoAsUser(
+                            clientPackageName, /* flags= */ 0, clientUserHandle)
+                    .uid;
+        } catch (PackageManager.NameNotFoundException e) {
+            return Process.INVALID_UID;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     @Override
@@ -85,21 +239,21 @@
             } else {
                 mProxyRecords.put(serviceProxy.mUniqueId, proxyRecord);
             }
-            setProviderState(buildProviderInfo());
+            updateProviderInfo();
         }
         updateSessionInfo();
         notifyProviderState();
-        notifySessionInfoUpdated();
+        notifyGlobalSessionInfoUpdated();
     }
 
     @Override
     public void onSystemProviderRoutesChanged(MediaRoute2ProviderInfo providerInfo) {
         synchronized (mLock) {
             mLastSystemProviderInfo = providerInfo;
-            setProviderState(buildProviderInfo());
+            updateProviderInfo();
         }
         updateSessionInfo();
-        notifySessionInfoUpdated();
+        notifyGlobalSessionInfoUpdated();
     }
 
     /**
@@ -116,10 +270,13 @@
             var builder = new RoutingSessionInfo.Builder(systemSessionInfo);
             mProxyRecords.values().stream()
                     .flatMap(ProviderProxyRecord::getRoutesStream)
-                    .map(MediaRoute2Info::getId)
+                    .map(MediaRoute2Info::getOriginalId)
                     .forEach(builder::addTransferableRoute);
             mSessionInfos.clear();
             mSessionInfos.add(builder.build());
+            for (var sessionRecords : mPackageNameToSessionRecord.values()) {
+                mSessionInfos.add(sessionRecords.mTranslatedSessionInfo);
+            }
         }
     }
 
@@ -129,13 +286,84 @@
      * provider services}.
      */
     @GuardedBy("mLock")
-    private MediaRoute2ProviderInfo buildProviderInfo() {
+    private void updateProviderInfo() {
         MediaRoute2ProviderInfo.Builder builder =
                 new MediaRoute2ProviderInfo.Builder(mLastSystemProviderInfo);
-        mProxyRecords.values().stream()
-                .flatMap(ProviderProxyRecord::getRoutesStream)
-                .forEach(builder::addRoute);
-        return builder.build();
+        mOriginalRouteIdToProviderId.clear();
+        for (var proxyRecord : mProxyRecords.values()) {
+            String proxyId = proxyRecord.mProxy.mUniqueId;
+            proxyRecord
+                    .getRoutesStream()
+                    .forEach(
+                            route -> {
+                                builder.addRoute(route);
+                                mOriginalRouteIdToProviderId.put(route.getOriginalId(), proxyId);
+                            });
+        }
+        setProviderState(builder.build());
+    }
+
+    @Override
+    /* package */ void notifyGlobalSessionInfoUpdated() {
+        if (mCallback == null) {
+            return;
+        }
+
+        RoutingSessionInfo sessionInfo;
+        Set<String> packageNamesWithRoutingSessionOverrides;
+        synchronized (mLock) {
+            if (mSessionInfos.isEmpty()) {
+                return;
+            }
+            packageNamesWithRoutingSessionOverrides = mPackageNameToSessionRecord.keySet();
+            sessionInfo = mSessionInfos.getFirst();
+        }
+
+        mCallback.onSessionUpdated(this, sessionInfo, packageNamesWithRoutingSessionOverrides);
+    }
+
+    private void onSessionOverrideUpdated(RoutingSessionInfo sessionInfo) {
+        // TODO: b/362507305 - Consider adding routes from other provider services. This is not a
+        // trivial change because a provider1-route to provider2-route transfer has seemingly two
+        // possible approachies. Either we first release the current session and then create the new
+        // one, in which case the audio is briefly going to leak through the system route. On the
+        // other hand, if we first create the provider2 session, then there will be a period during
+        // which there will be two overlapping routing policies asking for the exact same media
+        // stream.
+        var builder = new RoutingSessionInfo.Builder(sessionInfo);
+        mLastSystemProviderInfo.getRoutes().stream()
+                .map(MediaRoute2Info::getOriginalId)
+                .forEach(builder::addTransferableRoute);
+        mCallback.onSessionUpdated(
+                /* provider= */ this,
+                builder.build(),
+                /* packageNamesWithRoutingSessionOverrides= */ Set.of());
+    }
+
+    /**
+     * Equivalent to {@link #asSystemRouteId}, except it takes a unique route id instead of a
+     * original id.
+     */
+    private static String uniqueIdAsSystemRouteId(String providerId, String uniqueRouteId) {
+        return asSystemRouteId(providerId, MediaRouter2Utils.getOriginalId(uniqueRouteId));
+    }
+
+    /**
+     * Returns a unique {@link MediaRoute2Info#getOriginalId() original id} for this provider to
+     * publish system media routes from {@link MediaRoute2ProviderService provider services}.
+     *
+     * <p>This provider will publish system media routes as part of the system routing session.
+     * However, said routes may also support {@link MediaRoute2Info#FLAG_ROUTING_TYPE_REMOTE remote
+     * routing}, meaning we cannot use the same id, or there would be an id collision. As a result,
+     * we derive a {@link MediaRoute2Info#getOriginalId original id} that is unique among all
+     * original route ids used by this provider.
+     */
+    private static String asSystemRouteId(String providerId, String originalRouteId) {
+        return ROUTE_ID_PREFIX_SYSTEM
+                + ROUTE_ID_SYSTEM_SEPARATOR
+                + providerId
+                + ROUTE_ID_SYSTEM_SEPARATOR
+                + originalRouteId;
     }
 
     /**
@@ -145,14 +373,69 @@
      * @param mProxy The corresponding {@link MediaRoute2ProviderServiceProxy}.
      * @param mSystemMediaRoutes The last snapshot of routes from the service that support system
      *     media routing, as defined by {@link MediaRoute2Info#supportsSystemMediaRouting()}.
+     * @param mNewOriginalIdToSourceOriginalIdMap Maps the {@link #mSystemMediaRoutes} ids to the
+     *     original ids of corresponding {@link MediaRoute2ProviderService service} route.
      */
     private record ProviderProxyRecord(
             MediaRoute2ProviderServiceProxy mProxy,
-            Collection<MediaRoute2Info> mSystemMediaRoutes) {
+            Map<String, MediaRoute2Info> mSystemMediaRoutes,
+            Map<String, String> mNewOriginalIdToSourceOriginalIdMap) {
 
         /** Returns a stream representation of the {@link #mSystemMediaRoutes}. */
         public Stream<MediaRoute2Info> getRoutesStream() {
-            return mSystemMediaRoutes.stream();
+            return mSystemMediaRoutes.values().stream();
+        }
+
+        @Nullable
+        public MediaRoute2Info getRouteByOriginalId(String routeOriginalId) {
+            return mSystemMediaRoutes.get(routeOriginalId);
+        }
+
+        /**
+         * Requests the creation of a system media routing session.
+         *
+         * @param requestId The request id.
+         * @param uid The uid of the package whose media to route, or {@link Process#INVALID_UID} if
+         *     not applicable.
+         * @param packageName The name of the package whose media to route.
+         * @param originalRouteId The {@link MediaRoute2Info#getOriginalId() original route id} of
+         *     the route that should be initially selected.
+         * @param callback A {@link MediaRoute2ProviderServiceProxy.SystemMediaSessionCallback} for
+         *     events.
+         * @see MediaRoute2ProviderService#onCreateSystemRoutingSession
+         */
+        public void requestCreateSystemMediaSession(
+                long requestId,
+                int uid,
+                String packageName,
+                String originalRouteId,
+                SystemMediaSessionCallback callback) {
+            var targetRouteId = mNewOriginalIdToSourceOriginalIdMap.get(originalRouteId);
+            if (targetRouteId == null) {
+                Log.w(
+                        TAG,
+                        "Failed system media session creation due to lack of mapping for id: "
+                                + originalRouteId);
+                callback.onRequestFailed(
+                        requestId, MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE);
+            } else {
+                mProxy.requestCreateSystemMediaSession(
+                        requestId,
+                        uid,
+                        packageName,
+                        targetRouteId,
+                        /* sessionHints= */ null,
+                        callback);
+            }
+        }
+
+        public void requestTransfer(String sessionId, MediaRoute2Info targetRoute) {
+            // TODO: Map the target route to the source route original id.
+            throw new UnsupportedOperationException("TODO Implement");
+        }
+
+        public void releaseSession(long requestId, String originalSessionId) {
+            mProxy.releaseSession(requestId, originalSessionId);
         }
 
         /**
@@ -165,22 +448,177 @@
             if (providerInfo == null) {
                 return null;
             }
-            ArraySet<MediaRoute2Info> routes = new ArraySet<>();
-            providerInfo.getRoutes().stream()
-                    .filter(MediaRoute2Info::supportsSystemMediaRouting)
-                    .forEach(
-                            route -> {
-                                String id =
-                                        ROUTE_ID_PREFIX_SYSTEM
-                                                + route.getProviderId()
-                                                + ROUTE_ID_SYSTEM_SEPARATOR
-                                                + route.getOriginalId();
-                                routes.add(
-                                        new MediaRoute2Info.Builder(id, route.getName())
-                                                .addFeature(FEATURE_LIVE_AUDIO)
-                                                .build());
-                            });
-            return new ProviderProxyRecord(serviceProxy, Collections.unmodifiableSet(routes));
+            Map<String, MediaRoute2Info> routesMap = new ArrayMap<>();
+            Map<String, String> idMap = new ArrayMap<>();
+            for (MediaRoute2Info sourceRoute : providerInfo.getRoutes()) {
+                if (!sourceRoute.supportsSystemMediaRouting()) {
+                    continue;
+                }
+                String id =
+                        asSystemRouteId(providerInfo.getUniqueId(), sourceRoute.getOriginalId());
+                var newRoute =
+                        new MediaRoute2Info.Builder(id, sourceRoute.getName())
+                                .addFeature(FEATURE_LIVE_AUDIO)
+                                .build();
+                routesMap.put(id, newRoute);
+                idMap.put(id, sourceRoute.getOriginalId());
+            }
+            return new ProviderProxyRecord(
+                    serviceProxy,
+                    Collections.unmodifiableMap(routesMap),
+                    Collections.unmodifiableMap(idMap));
+        }
+    }
+
+    private class SystemMediaSessionCallbackImpl implements SystemMediaSessionCallback {
+
+        private final String mProviderId;
+        private final long mRequestId;
+        private final String mClientPackageName;
+        // Accessed only on mHandler.
+        @Nullable private SystemMediaSessionRecord mSessionRecord;
+
+        private SystemMediaSessionCallbackImpl(
+                String providerId, long requestId, String clientPackageName) {
+            mProviderId = providerId;
+            mRequestId = requestId;
+            mClientPackageName = clientPackageName;
+        }
+
+        @Override
+        public void onSessionUpdate(@NonNull RoutingSessionInfo sessionInfo) {
+            mHandler.post(
+                    () -> {
+                        if (mSessionRecord != null) {
+                            mSessionRecord.onSessionUpdate(sessionInfo);
+                        }
+                        SystemMediaSessionRecord systemMediaSessionRecord =
+                                new SystemMediaSessionRecord(mProviderId, sessionInfo);
+                        RoutingSessionInfo translatedSession;
+                        synchronized (mLock) {
+                            mSessionRecord = systemMediaSessionRecord;
+                            mPackageNameToSessionRecord.put(
+                                    mClientPackageName, systemMediaSessionRecord);
+                            mPendingSessionCreations.remove(mRequestId);
+                            translatedSession = systemMediaSessionRecord.mTranslatedSessionInfo;
+                        }
+                        onSessionOverrideUpdated(translatedSession);
+                    });
+        }
+
+        @Override
+        public void onRequestFailed(long requestId, @Reason int reason) {
+            mHandler.post(
+                    () -> {
+                        if (mSessionRecord != null) {
+                            mSessionRecord.onRequestFailed(requestId, reason);
+                        }
+                        synchronized (mLock) {
+                            mPendingSessionCreations.remove(mRequestId);
+                        }
+                        notifyRequestFailed(requestId, reason);
+                    });
+        }
+
+        @Override
+        public void onSessionReleased() {
+            mHandler.post(
+                    () -> {
+                        if (mSessionRecord != null) {
+                            mSessionRecord.onSessionReleased();
+                        } else {
+                            // Should never happen. The session hasn't yet been created.
+                            throw new IllegalStateException();
+                        }
+                    });
+        }
+    }
+
+    private class SystemMediaSessionRecord implements SystemMediaSessionCallback {
+
+        private final String mProviderId;
+
+        @GuardedBy("SystemMediaRoute2Provider2.this.mLock")
+        @NonNull
+        private RoutingSessionInfo mSourceSessionInfo;
+
+        /**
+         * The same as {@link #mSourceSessionInfo}, except ids are {@link #asSystemRouteId system
+         * provider ids}.
+         */
+        @GuardedBy("SystemMediaRoute2Provider2.this.mLock")
+        @NonNull
+        private RoutingSessionInfo mTranslatedSessionInfo;
+
+        SystemMediaSessionRecord(
+                @NonNull String providerId, @NonNull RoutingSessionInfo sessionInfo) {
+            mProviderId = providerId;
+            mSourceSessionInfo = sessionInfo;
+            mTranslatedSessionInfo = asSystemProviderSession(sessionInfo);
+        }
+
+        @Override
+        public void onSessionUpdate(@NonNull RoutingSessionInfo sessionInfo) {
+            RoutingSessionInfo translatedSessionInfo = mTranslatedSessionInfo;
+            synchronized (mLock) {
+                mSourceSessionInfo = sessionInfo;
+                mTranslatedSessionInfo = asSystemProviderSession(sessionInfo);
+            }
+            onSessionOverrideUpdated(translatedSessionInfo);
+        }
+
+        @Override
+        public void onRequestFailed(long requestId, @Reason int reason) {
+            notifyRequestFailed(requestId, reason);
+        }
+
+        @Override
+        public void onSessionReleased() {
+            synchronized (mLock) {
+                removeSelfFromSessionMap();
+            }
+            notifyGlobalSessionInfoUpdated();
+        }
+
+        @GuardedBy("SystemMediaRoute2Provider2.this.mLock")
+        @Nullable
+        public ProviderProxyRecord getProxyRecord() {
+            ProviderProxyRecord provider = mProxyRecords.get(mProviderId);
+            if (provider == null) {
+                // Unexpected condition where the proxy is no longer available while there's an
+                // ongoing session. Could happen due to a crash in the provider process.
+                removeSelfFromSessionMap();
+            }
+            return provider;
+        }
+
+        @GuardedBy("SystemMediaRoute2Provider2.this.mLock")
+        private void removeSelfFromSessionMap() {
+            mPackageNameToSessionRecord.remove(mSourceSessionInfo.getClientPackageName());
+        }
+
+        private RoutingSessionInfo asSystemProviderSession(RoutingSessionInfo session) {
+            var builder =
+                    new RoutingSessionInfo.Builder(session)
+                            .setProviderId(mUniqueId)
+                            .setSystemSession(true)
+                            .clearSelectedRoutes()
+                            .clearSelectableRoutes()
+                            .clearDeselectableRoutes()
+                            .clearTransferableRoutes();
+            session.getSelectedRoutes().stream()
+                    .map(it -> uniqueIdAsSystemRouteId(session.getProviderId(), it))
+                    .forEach(builder::addSelectedRoute);
+            session.getSelectableRoutes().stream()
+                    .map(it -> uniqueIdAsSystemRouteId(session.getProviderId(), it))
+                    .forEach(builder::addSelectableRoute);
+            session.getDeselectableRoutes().stream()
+                    .map(it -> uniqueIdAsSystemRouteId(session.getProviderId(), it))
+                    .forEach(builder::addDeselectableRoute);
+            session.getTransferableRoutes().stream()
+                    .map(it -> uniqueIdAsSystemRouteId(session.getProviderId(), it))
+                    .forEach(builder::addTransferableRoute);
+            return builder.build();
         }
     }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
index 89902f7..7cbbe29 100644
--- a/services/core/java/com/android/server/notification/NotificationDelegate.java
+++ b/services/core/java/com/android/server/notification/NotificationDelegate.java
@@ -101,4 +101,10 @@
     void onNotificationFeedbackReceived(String key, Bundle feedback);
 
     void prepareForPossibleShutdown();
+
+    /**
+     *  Called when the notification should be unbundled.
+     * @param key the notification key
+     */
+    void unbundleNotification(String key);
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c6d7fc7..7375a68 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -112,6 +112,7 @@
 import static android.service.notification.Flags.callstyleCallbackApi;
 import static android.service.notification.Flags.notificationClassification;
 import static android.service.notification.Flags.notificationForceGrouping;
+import static android.service.notification.Flags.notificationRegroupOnClassification;
 import static android.service.notification.Flags.redactSensitiveNotificationsBigTextStyle;
 import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
@@ -1851,6 +1852,42 @@
             }
         }
 
+        @Override
+        public void unbundleNotification(String key) {
+            if (!(notificationClassification() && notificationRegroupOnClassification())) {
+                return;
+            }
+            synchronized (mNotificationLock) {
+                NotificationRecord r = mNotificationsByKey.get(key);
+                if (r == null) {
+                    return;
+                }
+
+                if (DBG) {
+                    Slog.v(TAG, "unbundleNotification: " + r);
+                }
+
+                boolean hasOriginalSummary = false;
+                if (r.getSbn().isAppGroup() && r.getNotification().isGroupChild()) {
+                    final String oldGroupKey = GroupHelper.getFullAggregateGroupKey(
+                            r.getSbn().getPackageName(), r.getOriginalGroupKey(), r.getUserId());
+                    NotificationRecord groupSummary = mSummaryByGroupKey.get(oldGroupKey);
+                    // We only care about app-provided valid groups
+                    hasOriginalSummary = (groupSummary != null
+                            && !GroupHelper.isAggregatedGroup(groupSummary));
+                }
+
+                // Only NotificationRecord's mChannel is updated when bundled, the Notification
+                // mChannelId will always be the original channel.
+                String origChannelId = r.getNotification().getChannelId();
+                NotificationChannel originalChannel = mPreferencesHelper.getNotificationChannel(
+                        r.getSbn().getPackageName(), r.getUid(), origChannelId, false);
+                if (originalChannel != null && !origChannelId.equals(r.getChannel().getId())) {
+                    r.updateNotificationChannel(originalChannel);
+                    mGroupHelper.onNotificationUnbundled(r, hasOriginalSummary);
+                }
+            }
+        }
     };
 
     NotificationManagerPrivate mNotificationManagerPrivate = new NotificationManagerPrivate() {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 749952e..15377d6 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -559,7 +559,7 @@
 
             if (r.uid == UNKNOWN_UID) {
                 if (Flags.persistIncompleteRestoreData()) {
-                    r.userId = userId;
+                    r.userIdWhenUidUnknown = userId;
                 }
                 mRestoredWithoutUids.put(unrestoredPackageKey(pkg, userId), r);
             } else {
@@ -756,7 +756,7 @@
 
         if (Flags.persistIncompleteRestoreData() && r.uid == UNKNOWN_UID) {
             out.attributeLong(null, ATT_CREATION_TIME, r.creationTime);
-            out.attributeInt(null, ATT_USERID, r.userId);
+            out.attributeInt(null, ATT_USERID, r.userIdWhenUidUnknown);
         }
 
         if (!forBackup) {
@@ -1959,7 +1959,7 @@
         ArrayList<ZenBypassingApp> bypassing = new ArrayList<>();
         synchronized (mLock) {
             for (PackagePreferences p : mPackagePreferences.values()) {
-                if (p.userId != userId) {
+                if (UserHandle.getUserId(p.uid) != userId) {
                     continue;
                 }
                 int totalChannelCount = p.channels.size();
@@ -3189,7 +3189,7 @@
         // Until we enable the UI, we should return false.
         boolean canHavePromotedNotifs = android.app.Flags.uiRichOngoing();
 
-        @UserIdInt int userId;
+        @UserIdInt int userIdWhenUidUnknown;
 
         Delegate delegate = null;
         ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index c4d1cc7..ec0f251 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4068,7 +4068,7 @@
                     @Nullable IBinder focusedToken) {
                 boolean handled = PhoneWindowManager.this.handleKeyGestureEvent(event,
                         focusedToken);
-                if (handled && Arrays.stream(event.getKeycodes()).anyMatch(
+                if (handled && !event.isCancelled() && Arrays.stream(event.getKeycodes()).anyMatch(
                         (keycode) -> keycode == KeyEvent.KEYCODE_POWER)) {
                     mPowerKeyHandled = true;
                 }
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 0c3c46c..271c818 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -503,6 +503,31 @@
         }
     }
 
+    @VisibleForTesting
+    int getWakelockMonitorTypeForLogging(int flags) {
+        switch (flags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
+            case PowerManager.FULL_WAKE_LOCK, PowerManager.SCREEN_DIM_WAKE_LOCK,
+                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
+                return PowerManager.FULL_WAKE_LOCK;
+            case PowerManager.DRAW_WAKE_LOCK:
+                return PowerManager.DRAW_WAKE_LOCK;
+            case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
+                if (mSuspendWhenScreenOffDueToProximityConfig) {
+                    return -1;
+                }
+                return PowerManager.PARTIAL_WAKE_LOCK;
+            case PowerManager.PARTIAL_WAKE_LOCK:
+                return PowerManager.PARTIAL_WAKE_LOCK;
+            case PowerManager.DOZE_WAKE_LOCK:
+                // Doze wake locks are an internal implementation detail of the
+                // communication between dream manager service and power manager
+                // service.  They have no additive battery impact.
+                return -1;
+            default:
+                return -1;
+        }
+    }
+
     /**
      * Notifies that the device is changing wakefulness.
      * This function may be called even if the previous change hasn't finished in
@@ -1288,7 +1313,7 @@
         if (mBatteryStatsInternal == null) {
             return;
         }
-        final int type = flags & PowerManager.WAKE_LOCK_LEVEL_MASK;
+        final int type = getWakelockMonitorTypeForLogging(flags);
         if (workSource == null || workSource.isEmpty()) {
             final int mappedUid = mBatteryStatsInternal.getOwnerUid(ownerUid);
             mFrameworkStatsLogger.wakelockStateChanged(mappedUid, tag, type, eventType);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index f8877ad..c18918f 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -2175,6 +2175,19 @@
         }
     }
 
+    /**
+     *  Called when the notification should be unbundled.
+     * @param key the notification key
+     */
+    @Override
+    public void unbundleNotification(@Nullable String key) {
+        enforceStatusBarService();
+        enforceValidCallingUser();
+        Binder.withCleanCallingIdentity(() -> {
+            mNotificationDelegate.unbundleNotification(key);
+        });
+    }
+
 
     @Override
     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
diff --git a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
index dad3a78..bb163ef 100644
--- a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
+++ b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
@@ -369,10 +369,9 @@
                 tagName = parser.getName();
                 if (TAG_QUOTA.equals(tagName)) {
                     CacheQuotaHint request = getRequestFromXml(parser);
-                    if (request == null) {
-                        continue;
+                    if (request != null) {
+                        quotas.add(request);
                     }
-                    quotas.add(request);
                 }
             }
             eventType = parser.next();
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index 0369a0f..9f88bc9 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -164,15 +164,13 @@
 
         appCompatTaskInfo.setIsFromLetterboxDoubleTap(reachabilityOverrides.isFromDoubleTap());
 
+        appCompatTaskInfo.topActivityAppBounds.set(getAppBounds(top));
         final boolean isTopActivityLetterboxed = top.areBoundsLetterboxed();
         appCompatTaskInfo.setTopActivityLetterboxed(isTopActivityLetterboxed);
         if (isTopActivityLetterboxed) {
             final Rect bounds = top.getBounds();
-            final Rect appBounds = getAppBounds(top);
             appCompatTaskInfo.topActivityLetterboxWidth = bounds.width();
             appCompatTaskInfo.topActivityLetterboxHeight = bounds.height();
-            appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width();
-            appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height();
             // TODO(b/379824541) Remove duplicate information.
             appCompatTaskInfo.topActivityLetterboxBounds = bounds;
             // We need to consider if letterboxed or pillarboxed.
@@ -281,8 +279,7 @@
         info.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
         info.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET;
         info.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET;
-        info.topActivityLetterboxAppHeight = TaskInfo.PROPERTY_VALUE_UNSET;
-        info.topActivityLetterboxAppWidth = TaskInfo.PROPERTY_VALUE_UNSET;
+        info.topActivityAppBounds.setEmpty();
         info.topActivityLetterboxBounds = null;
         info.cameraCompatTaskInfo.freeformCameraCompatMode =
                 CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_UNSPECIFIED;
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index dd1af0a..6b6f011 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -29,9 +29,6 @@
 import android.view.WindowManager;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-
-import com.android.internal.R;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -687,11 +684,12 @@
 
     @Override
     public Animation getFadeInAnimation() {
+        final Animation anim = super.getFadeInAnimation();
         if (mHasScreenRotationAnimation) {
             // Use a shorter animation so it is easier to align with screen rotation animation.
-            return AnimationUtils.loadAnimation(mContext, R.anim.screen_rotate_0_enter);
+            anim.setDuration(getScaledDuration(SHORT_DURATION_MS));
         }
-        return super.getFadeInAnimation();
+        return anim;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/FadeAnimationController.java b/services/core/java/com/android/server/wm/FadeAnimationController.java
index 7af67e6..c60d367 100644
--- a/services/core/java/com/android/server/wm/FadeAnimationController.java
+++ b/services/core/java/com/android/server/wm/FadeAnimationController.java
@@ -20,41 +20,51 @@
 import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;
 
 import android.annotation.NonNull;
-import android.content.Context;
 import android.util.proto.ProtoOutputStream;
 import android.view.SurfaceControl;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Transformation;
 
-import com.android.internal.R;
-
 import java.io.PrintWriter;
 
 /**
  * An animation controller to fade-in/out for a window token.
  */
 public class FadeAnimationController {
+    static final int SHORT_DURATION_MS = 200;
+    static final int MEDIUM_DURATION_MS = 350;
+
     protected final DisplayContent mDisplayContent;
-    protected final Context mContext;
 
     public FadeAnimationController(DisplayContent displayContent) {
         mDisplayContent = displayContent;
-        mContext = displayContent.mWmService.mContext;
     }
 
     /**
      * @return a fade-in Animation.
      */
     public Animation getFadeInAnimation() {
-        return AnimationUtils.loadAnimation(mContext, R.anim.fade_in);
+        final AlphaAnimation anim = new AlphaAnimation(0f, 1f);
+        anim.setDuration(getScaledDuration(MEDIUM_DURATION_MS));
+        anim.setInterpolator(new DecelerateInterpolator());
+        return anim;
     }
 
     /**
      * @return a fade-out Animation.
      */
     public Animation getFadeOutAnimation() {
-        return AnimationUtils.loadAnimation(mContext, R.anim.fade_out);
+        final AlphaAnimation anim = new AlphaAnimation(1f, 0f);
+        anim.setDuration(getScaledDuration(SHORT_DURATION_MS));
+        anim.setInterpolator(new AccelerateInterpolator());
+        return anim;
+    }
+
+    long getScaledDuration(int durationMs) {
+        return (long) (durationMs * mDisplayContent.mWmService.getWindowAnimationScaleLocked());
     }
 
     /** Run the fade in/out animation for a window token. */
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9d9c53d..db62ceb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2945,7 +2945,7 @@
                 final DisplayContent dc = mRoot.getDisplayContent(displayId);
                 if (dc == null) {
                     if (callingPid != MY_PID) {
-                        throw new WindowManager.InvalidDisplayException(
+                        throw new IllegalArgumentException(
                                 "attachWindowContextToDisplayContent: trying to attach to a"
                                         + " non-existing display:" + displayId);
                     }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index f96294ed..b7b4f04 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -214,7 +214,8 @@
     private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests";
     private static final long STANDARD_DISPLAY_EVENTS =
             DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
-            | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+            | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+            | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE
             | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
     private static final long STANDARD_AND_CONNECTION_DISPLAY_EVENTS =
             STANDARD_DISPLAY_EVENTS
@@ -222,7 +223,7 @@
 
     private static final String EVENT_DISPLAY_ADDED = "EVENT_DISPLAY_ADDED";
     private static final String EVENT_DISPLAY_REMOVED = "EVENT_DISPLAY_REMOVED";
-    private static final String EVENT_DISPLAY_CHANGED = "EVENT_DISPLAY_CHANGED";
+    private static final String EVENT_DISPLAY_BASIC_CHANGED = "EVENT_DISPLAY_BASIC_CHANGED";
     private static final String EVENT_DISPLAY_BRIGHTNESS_CHANGED =
             "EVENT_DISPLAY_BRIGHTNESS_CHANGED";
     private static final String EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED =
@@ -889,7 +890,6 @@
 
         FakeDisplayManagerCallback callback = registerDisplayListenerCallback(
                 displayManager, bs, displayDevice);
-
         // Simulate DisplayDevice change
         DisplayDeviceInfo displayDeviceInfo2 = new DisplayDeviceInfo();
         displayDeviceInfo2.copyFrom(displayDeviceInfo);
@@ -900,7 +900,8 @@
 
         Handler handler = displayManager.getDisplayHandler();
         waitForIdleHandler(handler);
-        assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_CHANGED);
+        assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_BASIC_CHANGED,
+                EVENT_DISPLAY_REFRESH_RATE_CHANGED);
     }
 
     /**
@@ -2145,7 +2146,7 @@
                         new DisplayEventReceiver.FrameRateOverride(myUid, 30f),
                 });
         waitForIdleHandler(displayManager.getDisplayHandler());
-        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED);
+        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
         callback.clear();
 
         updateFrameRateOverride(displayManager, displayDevice,
@@ -2154,7 +2155,7 @@
                         new DisplayEventReceiver.FrameRateOverride(1234, 30f),
                 });
         waitForIdleHandler(displayManager.getDisplayHandler());
-        assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED);
+        assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED);
 
         updateFrameRateOverride(displayManager, displayDevice,
                 new DisplayEventReceiver.FrameRateOverride[]{
@@ -2163,7 +2164,7 @@
                         new DisplayEventReceiver.FrameRateOverride(5678, 30f),
                 });
         waitForIdleHandler(displayManager.getDisplayHandler());
-        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED);
+        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
         callback.clear();
 
         updateFrameRateOverride(displayManager, displayDevice,
@@ -2172,7 +2173,7 @@
                         new DisplayEventReceiver.FrameRateOverride(5678, 30f),
                 });
         waitForIdleHandler(displayManager.getDisplayHandler());
-        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED);
+        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
         callback.clear();
 
         updateFrameRateOverride(displayManager, displayDevice,
@@ -2180,7 +2181,7 @@
                         new DisplayEventReceiver.FrameRateOverride(5678, 30f),
                 });
         waitForIdleHandler(displayManager.getDisplayHandler());
-        assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED);
+        assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED);
     }
 
     /**
@@ -2303,16 +2304,16 @@
 
         updateRenderFrameRate(displayManager, displayDevice, 30f);
         waitForIdleHandler(displayManager.getDisplayHandler());
-        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED);
+        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
         callback.clear();
 
         updateRenderFrameRate(displayManager, displayDevice, 30f);
         waitForIdleHandler(displayManager.getDisplayHandler());
-        assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED);
+        assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
 
         updateRenderFrameRate(displayManager, displayDevice, 20f);
         waitForIdleHandler(displayManager.getDisplayHandler());
-        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED);
+        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
         callback.clear();
     }
 
@@ -3888,7 +3889,7 @@
         observer.onChange(false, Settings.Secure.getUriFor(MIRROR_BUILT_IN_DISPLAY));
         waitForIdleHandler(handler);
 
-        assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED);
+        assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED);
     }
 
     @Test
@@ -3919,7 +3920,7 @@
         observer.onChange(false, Settings.Secure.getUriFor(MIRROR_BUILT_IN_DISPLAY));
         waitForIdleHandler(handler);
 
-        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED);
+        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
     }
 
     private void initDisplayPowerController(DisplayManagerInternal localService) {
@@ -4389,8 +4390,8 @@
                     return EVENT_DISPLAY_ADDED;
                 case DisplayManagerGlobal.EVENT_DISPLAY_REMOVED:
                     return EVENT_DISPLAY_REMOVED;
-                case DisplayManagerGlobal.EVENT_DISPLAY_CHANGED:
-                    return EVENT_DISPLAY_CHANGED;
+                case DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED:
+                    return EVENT_DISPLAY_BASIC_CHANGED;
                 case DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED:
                     return EVENT_DISPLAY_BRIGHTNESS_CHANGED;
                 case DisplayManagerGlobal.EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED:
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
index 5d42713..c65024f8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
@@ -26,6 +26,7 @@
 import org.mockito.ArgumentMatchers.anyFloat
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
 import org.mockito.kotlin.verify
@@ -43,7 +44,7 @@
 
     @Before
     fun setUp() {
-        displayInfo.displayId = 2
+        displayInfo.displayId = Display.DEFAULT_DISPLAY
         displayInfo.logicalWidth = 300
         displayInfo.logicalHeight = 200
         displayInfo.logicalDensityDpi = 100
@@ -90,6 +91,44 @@
     }
 
     @Test
+    fun updateDisplay() {
+        whenever(mockTopology.updateDisplay(eq(Display.DEFAULT_DISPLAY), anyFloat(), anyFloat()))
+            .thenReturn(true)
+
+        coordinator.onDisplayChanged(displayInfo)
+
+        verify(mockTopologyChangedCallback).invoke(mockTopologyCopy)
+    }
+
+    @Test
+    fun updateDisplay_notChanged() {
+        whenever(mockTopology.updateDisplay(eq(Display.DEFAULT_DISPLAY), anyFloat(), anyFloat()))
+            .thenReturn(false)
+
+        coordinator.onDisplayChanged(displayInfo)
+
+        verify(mockTopologyChangedCallback, never()).invoke(any())
+    }
+
+    @Test
+    fun removeDisplay() {
+        whenever(mockTopology.removeDisplay(Display.DEFAULT_DISPLAY)).thenReturn(true)
+
+        coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY)
+
+        verify(mockTopologyChangedCallback).invoke(mockTopologyCopy)
+    }
+
+    @Test
+    fun removeDisplay_notChanged() {
+        whenever(mockTopology.removeDisplay(Display.DEFAULT_DISPLAY)).thenReturn(false)
+
+        coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY)
+
+        verify(mockTopologyChangedCallback, never()).invoke(any())
+    }
+
+    @Test
     fun getTopology_copy() {
         assertThat(coordinator.topology).isEqualTo(mockTopologyCopy)
     }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index ad30f22..0dbb6ba 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -36,9 +36,9 @@
 import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
 import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CONNECTED;
 import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_DISCONNECTED;
-import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED;
 import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
 import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_STATE_CHANGED;
+import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED;
 import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
 import static com.android.server.display.layout.Layout.Display.POSITION_UNKNOWN;
 import static com.android.server.utils.FoldSettingProvider.SETTING_VALUE_SELECTIVE_STAY_AWAKE;
@@ -1170,17 +1170,19 @@
 
     @Test
     public void updateAndGetMaskForDisplayPropertyChanges_getsPropertyChangedFlags() {
-        // Change the display state
+        // Change the refresh rate override
         DisplayInfo newDisplayInfo = new DisplayInfo();
+        newDisplayInfo.refreshRateOverride = 30;
+        assertEquals(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED,
+                mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo));
+
+        // Change the display state
+        when(mFlagsMock.isDisplayListenerPerformanceImprovementsEnabled()).thenReturn(true);
+        newDisplayInfo = new DisplayInfo();
         newDisplayInfo.state = STATE_OFF;
         assertEquals(LOGICAL_DISPLAY_EVENT_STATE_CHANGED,
                 mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo));
 
-        // Change the refresh rate override
-        newDisplayInfo = new DisplayInfo();
-        newDisplayInfo.refreshRateOverride = 30;
-        assertEquals(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED,
-                mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo));
 
         // Change multiple properties
         newDisplayInfo = new DisplayInfo();
diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
index 96741e0..469bd66 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
@@ -21,6 +21,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
@@ -889,6 +890,32 @@
                 "my.package.name", false, null, null);
     }
 
+    @Test
+    public void getWakelockMonitorTypeForLogging_evaluatesWakelockLevel() {
+        createNotifier();
+        assertEquals(mNotifier.getWakelockMonitorTypeForLogging(PowerManager.SCREEN_DIM_WAKE_LOCK),
+                PowerManager.FULL_WAKE_LOCK);
+        assertEquals(mNotifier.getWakelockMonitorTypeForLogging(
+                PowerManager.SCREEN_BRIGHT_WAKE_LOCK), PowerManager.FULL_WAKE_LOCK);
+        assertEquals(mNotifier.getWakelockMonitorTypeForLogging(PowerManager.DRAW_WAKE_LOCK),
+                PowerManager.DRAW_WAKE_LOCK);
+        assertEquals(mNotifier.getWakelockMonitorTypeForLogging(PowerManager.PARTIAL_WAKE_LOCK),
+                PowerManager.PARTIAL_WAKE_LOCK);
+        assertEquals(mNotifier.getWakelockMonitorTypeForLogging(
+                        PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK),
+                PowerManager.PARTIAL_WAKE_LOCK);
+        assertEquals(mNotifier.getWakelockMonitorTypeForLogging(
+                        PowerManager.DOZE_WAKE_LOCK), -1);
+
+        when(mResourcesSpy.getBoolean(
+                com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity))
+                .thenReturn(true);
+
+        createNotifier();
+        assertEquals(mNotifier.getWakelockMonitorTypeForLogging(
+                        PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK), -1);
+    }
+
     private final PowerManagerService.Injector mInjector = new PowerManagerService.Injector() {
         @Override
         Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 2fe6918..7dbbff2 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -33,6 +33,7 @@
 import static com.android.server.am.UserController.CLEAR_USER_JOURNEY_SESSION_MSG;
 import static com.android.server.am.UserController.COMPLETE_USER_SWITCH_MSG;
 import static com.android.server.am.UserController.CONTINUE_USER_SWITCH_MSG;
+import static com.android.server.am.UserController.DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS;
 import static com.android.server.am.UserController.REPORT_LOCKED_BOOT_COMPLETE_MSG;
 import static com.android.server.am.UserController.REPORT_USER_SWITCH_COMPLETE_MSG;
 import static com.android.server.am.UserController.REPORT_USER_SWITCH_MSG;
@@ -94,6 +95,7 @@
 import android.os.Message;
 import android.os.PowerManagerInternal;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.IStorageManager;
@@ -181,14 +183,12 @@
             Intent.ACTION_USER_STARTING);
 
     private static final Set<Integer> START_FOREGROUND_USER_MESSAGE_CODES = newHashSet(
-            0, // for startUserInternalOnHandler
             REPORT_USER_SWITCH_MSG,
             USER_SWITCH_TIMEOUT_MSG,
             USER_START_MSG,
             USER_CURRENT_MSG);
 
     private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
-            0, // for startUserInternalOnHandler
             USER_START_MSG,
             REPORT_LOCKED_BOOT_COMPLETE_MSG);
 
@@ -376,7 +376,7 @@
         // and the cascade effect goes on...). In fact, a better approach would to not assert the
         // binder calls, but their side effects (in this case, that the user is stopped right away)
         assertWithMessage("wrong binder message calls").that(mInjector.mHandler.getMessageCodes())
-                .containsExactly(/* for startUserInternalOnHandler */ 0, USER_START_MSG);
+                .containsExactly(USER_START_MSG);
     }
 
     private void startUserAssertions(
@@ -419,17 +419,12 @@
     @Test
     public void testDispatchUserSwitch() throws RemoteException {
         // Prepare mock observer and register it
-        IUserSwitchObserver observer = mock(IUserSwitchObserver.class);
-        when(observer.asBinder()).thenReturn(new Binder());
-        doAnswer(invocation -> {
-            IRemoteCallback callback = (IRemoteCallback) invocation.getArguments()[1];
-            callback.sendResult(null);
-            return null;
-        }).when(observer).onUserSwitching(anyInt(), any());
-        mUserController.registerUserSwitchObserver(observer, "mock");
+        IUserSwitchObserver observer = registerUserSwitchObserver(
+                /* replyToOnBeforeUserSwitchingCallback= */ true,
+                /* replyToOnUserSwitchingCallback= */ true);
         // Start user -- this will update state of mUserController
         mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
-        verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID));
+        verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID), any());
         Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
         assertNotNull(reportMsg);
         UserState userState = (UserState) reportMsg.obj;
@@ -453,14 +448,26 @@
     }
 
     @Test
+    public void testShouldCrashWhenOnBeforeUserSwitchingTimeouts() throws RemoteException {
+        IUserSwitchObserver observer = registerUserSwitchObserver(
+                /* replyToOnBeforeUserSwitchingCallback= */ false,
+                /* replyToOnUserSwitchingCallback= */ true);
+        mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
+        verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID), any());
+        assertThrows("Should have crashed when observers don't reply to onBeforeUserSwitching in "
+                        + DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS + " ms", RuntimeException.class,
+                mInjector.mHandler::runPendingCallbacks);
+    }
+
+    @Test
     public void testDispatchUserSwitchBadReceiver() throws RemoteException {
-        // Prepare mock observer which doesn't notify the callback and register it
-        IUserSwitchObserver observer = mock(IUserSwitchObserver.class);
-        when(observer.asBinder()).thenReturn(new Binder());
-        mUserController.registerUserSwitchObserver(observer, "mock");
+        // Prepare mock observer which doesn't notify the onUserSwitching callback and register it
+        IUserSwitchObserver observer = registerUserSwitchObserver(
+                /* replyToOnBeforeUserSwitchingCallback= */ true,
+                /* replyToOnUserSwitchingCallback= */ false);
         // Start user -- this will update state of mUserController
         mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
-        verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID));
+        verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID), any());
         Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
         assertNotNull(reportMsg);
         UserState userState = (UserState) reportMsg.obj;
@@ -551,7 +558,6 @@
         expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG);
         if (backgroundUserStopping) {
             expectedCodes.add(CLEAR_USER_JOURNEY_SESSION_MSG);
-            expectedCodes.add(0); // this is for directly posting in stopping.
         }
         if (expectScheduleBackgroundUserStopping) {
             expectedCodes.add(SCHEDULED_STOP_BACKGROUND_USER_MSG);
@@ -567,9 +573,9 @@
     @Test
     public void testDispatchUserSwitchComplete() throws RemoteException {
         // Prepare mock observer and register it
-        IUserSwitchObserver observer = mock(IUserSwitchObserver.class);
-        when(observer.asBinder()).thenReturn(new Binder());
-        mUserController.registerUserSwitchObserver(observer, "mock");
+        IUserSwitchObserver observer = registerUserSwitchObserver(
+                /* replyToOnBeforeUserSwitchingCallback= */ true,
+                /* replyToOnUserSwitchingCallback= */ true);
         // Start user -- this will update state of mUserController
         mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
         Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -1752,6 +1758,29 @@
         verify(mInjector, never()).onSystemUserVisibilityChanged(anyBoolean());
     }
 
+    private IUserSwitchObserver registerUserSwitchObserver(
+            boolean replyToOnBeforeUserSwitchingCallback, boolean replyToOnUserSwitchingCallback)
+            throws RemoteException {
+        IUserSwitchObserver observer = mock(IUserSwitchObserver.class);
+        when(observer.asBinder()).thenReturn(new Binder());
+        if (replyToOnBeforeUserSwitchingCallback) {
+            doAnswer(invocation -> {
+                IRemoteCallback callback = (IRemoteCallback) invocation.getArguments()[1];
+                callback.sendResult(null);
+                return null;
+            }).when(observer).onBeforeUserSwitching(anyInt(), any());
+        }
+        if (replyToOnUserSwitchingCallback) {
+            doAnswer(invocation -> {
+                IRemoteCallback callback = (IRemoteCallback) invocation.getArguments()[1];
+                callback.sendResult(null);
+                return null;
+            }).when(observer).onUserSwitching(anyInt(), any());
+        }
+        mUserController.registerUserSwitchObserver(observer, "mock");
+        return observer;
+    }
+
     // Should be public to allow mocking
     private static class TestInjector extends UserController.Injector {
         public final TestHandler mHandler;
@@ -1957,6 +1986,7 @@
          * fix this, but in the meantime, this is your warning.
          */
         private final List<Message> mMessages = new ArrayList<>();
+        private final List<Runnable> mPendingCallbacks = new ArrayList<>();
 
         TestHandler(Looper looper) {
             super(looper);
@@ -1989,14 +2019,24 @@
 
         @Override
         public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
-            Message copy = new Message();
-            copy.copyFrom(msg);
-            mMessages.add(copy);
-            if (msg.getCallback() != null) {
-                msg.getCallback().run();
+            if (msg.getCallback() == null) {
+                Message copy = new Message();
+                copy.copyFrom(msg);
+                mMessages.add(copy);
+            } else {
+                if (SystemClock.uptimeMillis() >= uptimeMillis) {
+                    msg.getCallback().run();
+                } else {
+                    mPendingCallbacks.add(msg.getCallback());
+                }
                 msg.setCallback(null);
             }
             return super.sendMessageAtTime(msg, uptimeMillis);
         }
+
+        private void runPendingCallbacks() {
+            mPendingCallbacks.forEach(Runnable::run);
+            mPendingCallbacks.clear();
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java b/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java
index 9c61d95..9528a05 100644
--- a/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java
@@ -23,13 +23,13 @@
 import android.util.Pair;
 import android.util.Xml;
 
-import com.android.internal.util.FastXmlSerializer;
 import com.android.modules.utils.TypedXmlSerializer;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.ByteArrayInputStream;
 import java.io.StringWriter;
@@ -123,8 +123,24 @@
                 buildCacheQuotaHint("uuid2", 10, 250));
     }
 
+    @Test
+    public void testReadInvalidInput() throws Exception {
+        String input = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" +
+                "<cache-info previousBytes=\"1000\">\n"
+                + "<quota/>\n"
+                + "</cache-info>\n";
+
+        try {
+            CacheQuotaStrategy.readFromXml(new ByteArrayInputStream(
+                    input.getBytes("UTF-8")));
+            fail("Expected XML parsing exception");
+        } catch (XmlPullParserException e) {
+            // Expected XmlPullParserException exception
+        }
+    }
+
     private CacheQuotaHint buildCacheQuotaHint(String volumeUuid, int uid, long quota) {
         return new CacheQuotaHint.Builder()
                 .setVolumeUuid(volumeUuid).setUid(uid).setQuota(quota).build();
     }
-}
\ No newline at end of file
+}
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 20f4bb6..601023f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -17683,4 +17683,145 @@
 
         assertThat(mService.mNotificationList).isEmpty();
     }
+
+    @Test
+    @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION,
+            FLAG_NOTIFICATION_FORCE_GROUPING,
+            FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+    public void testUnbundleNotification_ungrouped_restoresOriginalChannel() throws Exception {
+        NotificationManagerService.WorkerHandler handler = mock(
+                NotificationManagerService.WorkerHandler.class);
+        mService.setHandler(handler);
+        when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+        when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+        when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
+        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
+
+        // Post a single notification
+        final boolean hasOriginalSummary = false;
+        final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+        final String keyToUnbundle = r.getKey();
+        mService.addNotification(r);
+
+        // Classify notification into the NEWS bundle
+        Bundle signals = new Bundle();
+        signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS);
+        Adjustment adjustment = new Adjustment(
+                r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
+        mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+        waitForIdle();
+        r.applyAdjustments();
+        // Check that the NotificationRecord channel is updated
+        assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
+        // Check that the Notification mChannelId is not updated
+        assertThat(r.getNotification().getChannelId()).isEqualTo(TEST_CHANNEL_ID);
+
+        // Unbundle the notification
+        mService.mNotificationDelegate.unbundleNotification(keyToUnbundle);
+
+        // Check that the original channel was restored
+        assertThat(r.getChannel().getId()).isEqualTo(TEST_CHANNEL_ID);
+        verify(mGroupHelper, times(1)).onNotificationUnbundled(eq(r), eq(hasOriginalSummary));
+    }
+
+    @Test
+    @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION,
+            FLAG_NOTIFICATION_FORCE_GROUPING,
+            FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+    public void testUnbundleNotification_grouped_restoresOriginalChannel() throws Exception {
+        NotificationManagerService.WorkerHandler handler = mock(
+                NotificationManagerService.WorkerHandler.class);
+        mService.setHandler(handler);
+        when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+        when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+        when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
+        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
+
+        // Post grouped notifications
+        final String originalGroupName = "originalGroup";
+        final int summaryId = 0;
+        final NotificationRecord r1 = generateNotificationRecord(mTestNotificationChannel,
+                summaryId + 1, originalGroupName, false);
+        mService.addNotification(r1);
+        final NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel,
+                summaryId + 2, originalGroupName, false);
+        mService.addNotification(r2);
+        final NotificationRecord summary = generateNotificationRecord(mTestNotificationChannel,
+                summaryId, originalGroupName, true);
+        mService.addNotification(summary);
+        final String originalGroupKey = summary.getGroupKey();
+        assertThat(mService.mSummaryByGroupKey).containsEntry(originalGroupKey, summary);
+
+        // Classify a child notification into the NEWS bundle
+        final String keyToUnbundle = r1.getKey();
+        final boolean hasOriginalSummary = true;
+        Bundle signals = new Bundle();
+        signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS);
+        Adjustment adjustment = new Adjustment(r1.getSbn().getPackageName(), r1.getKey(), signals,
+                "", r1.getUser().getIdentifier());
+        mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+        waitForIdle();
+        r1.applyAdjustments();
+        assertThat(r1.getChannel().getId()).isEqualTo(NEWS_ID);
+
+        // Unbundle the notification
+        mService.mNotificationDelegate.unbundleNotification(keyToUnbundle);
+
+        // Check that the original channel was restored
+        assertThat(r1.getChannel().getId()).isEqualTo(TEST_CHANNEL_ID);
+        verify(mGroupHelper, times(1)).onNotificationUnbundled(eq(r1), eq(hasOriginalSummary));
+    }
+
+    @Test
+    @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION,
+        FLAG_NOTIFICATION_FORCE_GROUPING,
+        FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+    public void testUnbundleNotification_groupedSummaryCanceled_restoresOriginalChannel()
+            throws Exception {
+        NotificationManagerService.WorkerHandler handler = mock(
+                NotificationManagerService.WorkerHandler.class);
+        mService.setHandler(handler);
+        when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+        when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+        when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
+        when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
+
+        // Post grouped notifications
+        final String originalGroupName = "originalGroup";
+        final int summaryId = 0;
+        final NotificationRecord r1 = generateNotificationRecord(mTestNotificationChannel,
+                summaryId + 1, originalGroupName, false);
+        mService.addNotification(r1);
+        final NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel,
+                summaryId + 2, originalGroupName, false);
+        mService.addNotification(r2);
+        final NotificationRecord summary = generateNotificationRecord(mTestNotificationChannel,
+                summaryId, originalGroupName, true);
+        mService.addNotification(summary);
+        final String originalGroupKey = summary.getGroupKey();
+        assertThat(mService.mSummaryByGroupKey).containsEntry(originalGroupKey, summary);
+
+        // Classify a child notification into the NEWS bundle
+        final String keyToUnbundle = r1.getKey();
+        Bundle signals = new Bundle();
+        signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS);
+        Adjustment adjustment = new Adjustment(r1.getSbn().getPackageName(), r1.getKey(), signals,
+                "", r1.getUser().getIdentifier());
+        mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+        waitForIdle();
+        r1.applyAdjustments();
+        assertThat(r1.getChannel().getId()).isEqualTo(NEWS_ID);
+
+        // Cancel original summary
+        final boolean hasOriginalSummary = false;
+        mService.mSummaryByGroupKey.remove(summary.getGroupKey());
+
+        // Unbundle the notification
+        mService.mNotificationDelegate.unbundleNotification(keyToUnbundle);
+
+        // Check that the original channel was restored
+        assertThat(r1.getChannel().getId()).isEqualTo(TEST_CHANNEL_ID);
+        verify(mGroupHelper, times(1)).onNotificationUnbundled(eq(r1), eq(hasOriginalSummary));
+    }
+
 }
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 fbd53f7..8e79514 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -66,7 +66,6 @@
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
 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 static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
 import static com.android.server.notification.Flags.FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI;
 import static com.android.server.notification.Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA;
 import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER;
@@ -155,7 +154,6 @@
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
 import com.android.internal.config.sysui.TestableFlagResolver;
@@ -167,9 +165,6 @@
 import com.android.server.UiServiceTestCase;
 import com.android.server.notification.PermissionHelper.PackagePermission;
 
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.protobuf.InvalidProtocolBufferException;
@@ -204,6 +199,9 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ThreadLocalRandom;
 
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4.class)
 @EnableFlags(FLAG_PERSIST_INCOMPLETE_RESTORE_DATA)
@@ -2640,6 +2638,35 @@
     }
 
     @Test
+    public void getPackagesBypassingDnd_multipleUsers() {
+        int uidUser1 = UserHandle.getUid(1, UID_P);
+        NotificationChannel channelUser1Bypass = new NotificationChannel("id11", "name1",
+                NotificationManager.IMPORTANCE_MAX);
+        channelUser1Bypass.setBypassDnd(true);
+        NotificationChannel channelUser1NoBypass = new NotificationChannel("id12", "name2",
+                NotificationManager.IMPORTANCE_MAX);
+        channelUser1NoBypass.setBypassDnd(false);
+
+        int uidUser2 = UserHandle.getUid(2, UID_P);
+        NotificationChannel channelUser2Bypass = new NotificationChannel("id21", "name1",
+                NotificationManager.IMPORTANCE_MAX);
+        channelUser2Bypass.setBypassDnd(true);
+
+        mHelper.createNotificationChannel(PKG_P, uidUser1, channelUser1Bypass, true,
+                /* hasDndAccess= */ true, uidUser1, false);
+        mHelper.createNotificationChannel(PKG_P, uidUser1, channelUser1NoBypass, true,
+                /* hasDndAccess= */ true, uidUser1, false);
+        mHelper.createNotificationChannel(PKG_P, uidUser2, channelUser2Bypass, true,
+                /* hasDndAccess= */ true, uidUser2, false);
+
+        assertThat(mHelper.getPackagesBypassingDnd(0)).isEmpty();
+        assertThat(mHelper.getPackagesBypassingDnd(1))
+                .containsExactly(new ZenBypassingApp(PKG_P, false));
+        assertThat(mHelper.getPackagesBypassingDnd(2))
+                .containsExactly(new ZenBypassingApp(PKG_P, true));
+    }
+
+    @Test
     public void getPackagesBypassingDnd_oneChannelBypassing_groupBlocked() {
         int uid = UID_N_MR1;
         NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
diff --git a/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
index 34f0c19..fe9f636 100644
--- a/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
+++ b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
@@ -76,6 +76,7 @@
     private ActivityTestRule<EmptyActivity> mEmptyActivityRule =
             new ActivityTestRule<>(EmptyActivity.class, false , true);
 
+
     @Before
     public void setUp() {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -163,7 +164,7 @@
         // of that state.
         for (int i = 0; i < uiStates.size(); i++) {
             StateTracker.StateData stateData = uiStates.get(i);
-            if (stateData.mWidgetCategory.equals(AppJankStats.ANIMATION)) {
+            if (stateData.mWidgetCategory.equals(AppJankStats.WIDGET_CATEGORY_ANIMATION)) {
                 assertNotEquals(Long.MAX_VALUE, stateData.mVsyncIdEnd);
             }
         }
diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java
index 30c568b..c905957 100644
--- a/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java
@@ -215,7 +215,8 @@
         assertEquals(jankStats.getJankyFrameCount() * 2, pendingStat.getJankyFrames());
         assertEquals(jankStats.getTotalFrameCount() * 2, pendingStat.getTotalFrames());
 
-        int[] originalHistogramBuckets = jankStats.getFrameOverrunHistogram().getBucketCounters();
+        int[] originalHistogramBuckets =
+                jankStats.getRelativeFrameTimeHistogram().getBucketCounters();
         int[] frameOverrunBuckets = pendingStat.getFrameOverrunBuckets();
 
         for (int i = 0; i < frameOverrunBuckets.length; i++) {
diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java b/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java
index 0b4d97e..df92898 100644
--- a/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java
@@ -17,7 +17,7 @@
 package android.app.jank.tests;
 
 import android.app.jank.AppJankStats;
-import android.app.jank.FrameOverrunHistogram;
+import android.app.jank.RelativeFrameTimeHistogram;
 
 public class JankUtils {
     private static final int APP_ID = 25;
@@ -29,8 +29,8 @@
         AppJankStats jankStats = new AppJankStats(
                 /*App Uid*/APP_ID,
                 /*Widget Id*/"test widget id",
-                /*Widget Category*/AppJankStats.SCROLL,
-                /*Widget State*/AppJankStats.SCROLLING,
+                /*Widget Category*/AppJankStats.WIDGET_CATEGORY_SCROLL,
+                /*Widget State*/AppJankStats.WIDGET_STATE_SCROLLING,
                 /*Total Frames*/100,
                 /*Janky Frames*/25,
                 getOverrunHistogram()
@@ -41,12 +41,12 @@
     /**
      * Returns a mock histogram to be used with an AppJankStats object.
      */
-    public static FrameOverrunHistogram getOverrunHistogram() {
-        FrameOverrunHistogram overrunHistogram = new FrameOverrunHistogram();
-        overrunHistogram.addFrameOverrunMillis(-2);
-        overrunHistogram.addFrameOverrunMillis(1);
-        overrunHistogram.addFrameOverrunMillis(5);
-        overrunHistogram.addFrameOverrunMillis(25);
+    public static RelativeFrameTimeHistogram getOverrunHistogram() {
+        RelativeFrameTimeHistogram overrunHistogram = new RelativeFrameTimeHistogram();
+        overrunHistogram.addRelativeFrameTimeMillis(-2);
+        overrunHistogram.addRelativeFrameTimeMillis(1);
+        overrunHistogram.addRelativeFrameTimeMillis(5);
+        overrunHistogram.addRelativeFrameTimeMillis(25);
         return overrunHistogram;
     }
 }
diff --git a/tests/AppJankTest/src/android/app/jank/tests/TestWidget.java b/tests/AppJankTest/src/android/app/jank/tests/TestWidget.java
index 5fff460..71796d6 100644
--- a/tests/AppJankTest/src/android/app/jank/tests/TestWidget.java
+++ b/tests/AppJankTest/src/android/app/jank/tests/TestWidget.java
@@ -45,8 +45,8 @@
      */
     public void simulateAnimationStarting() {
         if (jankTrackerCreated()) {
-            mJankTracker.addUiState(AppJankStats.ANIMATION,
-                    Integer.toString(this.getId()), AppJankStats.ANIMATING);
+            mJankTracker.addUiState(AppJankStats.WIDGET_CATEGORY_ANIMATION,
+                    Integer.toString(this.getId()), AppJankStats.WIDGET_STATE_ANIMATING);
         }
     }
 
@@ -55,8 +55,8 @@
      */
     public void simulateAnimationEnding() {
         if (jankTrackerCreated()) {
-            mJankTracker.removeUiState(AppJankStats.ANIMATION,
-                    Integer.toString(this.getId()), AppJankStats.ANIMATING);
+            mJankTracker.removeUiState(AppJankStats.WIDGET_CATEGORY_ANIMATION,
+                    Integer.toString(this.getId()), AppJankStats.WIDGET_STATE_ANIMATING);
         }
     }
 
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
index 700856c..14c8de8 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
@@ -819,7 +819,7 @@
     private List<Float> getExpectedFrameRateForCompatibility(int compatibility) {
         assumeTrue("**** testSurfaceControlFrameRateCompatibility SKIPPED for compatibility "
                         + compatibility,
-                compatibility == Surface.FRAME_RATE_COMPATIBILITY_GTE);
+                compatibility == Surface.FRAME_RATE_COMPATIBILITY_AT_LEAST);
 
         Display display = getDisplay();
         List<Float> expectedFrameRates = getRefreshRates(display.getMode(), display)
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
index 4d48276..f1d4dc6 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
@@ -85,7 +85,8 @@
     @Test
     public void testSurfaceControlFrameRateCompatibilityGte() throws InterruptedException {
         GraphicsActivity activity = mActivityRule.getActivity();
-        activity.testSurfaceControlFrameRateCompatibility(Surface.FRAME_RATE_COMPATIBILITY_GTE);
+        activity.testSurfaceControlFrameRateCompatibility(
+                Surface.FRAME_RATE_COMPATIBILITY_AT_LEAST);
     }
 
     @Test
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 2e7b207..2db8b1e 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -29,6 +29,7 @@
 import android.tools.traces.parsers.WindowManagerStateHelper
 import android.tools.traces.wm.WindowingMode
 import android.view.KeyEvent.KEYCODE_LEFT_BRACKET
+import android.view.KeyEvent.KEYCODE_MINUS
 import android.view.KeyEvent.KEYCODE_RIGHT_BRACKET
 import android.view.KeyEvent.META_META_ON
 import android.view.WindowInsets
@@ -160,10 +161,21 @@
             ?: error("Unable to find resource $MINIMIZE_BUTTON_VIEW\n")
     }
 
-    fun minimizeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice, isPip: Boolean = false) {
-        val caption = getCaptionForTheApp(wmHelper, device)
-        val minimizeButton = getMinimizeButtonForTheApp(caption)
-        minimizeButton.click()
+    fun minimizeDesktopApp(
+        wmHelper: WindowManagerStateHelper,
+        device: UiDevice,
+        isPip: Boolean = false,
+        usingKeyboard: Boolean = false,
+    ) {
+        if (usingKeyboard) {
+            val keyEventHelper = KeyEventHelper(getInstrumentation())
+            keyEventHelper.press(KEYCODE_MINUS, META_META_ON)
+        } else {
+            val caption = getCaptionForTheApp(wmHelper, device)
+            val minimizeButton = getMinimizeButtonForTheApp(caption)
+            minimizeButton.click()
+        }
+
         wmHelper
             .StateSyncBuilder()
             .withAppTransitionIdle()
@@ -226,8 +238,7 @@
         toLeft: Boolean,
     ) {
         val bracketKey = if (toLeft) KEYCODE_LEFT_BRACKET else KEYCODE_RIGHT_BRACKET
-        keyEventHelper.actionDown(bracketKey, META_META_ON)
-        keyEventHelper.actionUp(bracketKey, META_META_ON)
+        keyEventHelper.press(bracketKey, META_META_ON)
         waitAndVerifySnapResize(wmHelper, context, toLeft)
     }
 
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/KeyEventHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/KeyEventHelper.kt
index ebd8cc3..55ed091 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/KeyEventHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/KeyEventHelper.kt
@@ -29,6 +29,10 @@
 class KeyEventHelper(
     private val instr: Instrumentation,
 ) {
+    fun press(keyCode: Int, metaState: Int = 0) {
+        actionDown(keyCode, metaState)
+        actionUp(keyCode, metaState)
+    }
 
     fun actionDown(keyCode: Int, metaState: Int = 0, time: Long = SystemClock.uptimeMillis()) {
         injectKeyEvent(ACTION_DOWN, keyCode, metaState, downTime = time, eventTime = time)
diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp
index 5e0f87f..60c4bf5 100644
--- a/tools/aapt/Package.cpp
+++ b/tools/aapt/Package.cpp
@@ -292,13 +292,12 @@
             }
             if (!hasData) {
                 const String8& srcName = file->getSourceFile();
-                time_t fileModWhen;
-                fileModWhen = getFileModDate(srcName.c_str());
-                if (fileModWhen == (time_t) -1) { // file existence tested earlier,
-                    return false;                 //  not expecting an error here
+                auto fileModWhen = getFileModDate(srcName.c_str());
+                if (fileModWhen == kInvalidModDate) { // file existence tested earlier,
+                    return false;                     //  not expecting an error here
                 }
-    
-                if (fileModWhen > entry->getModWhen()) {
+
+                if (toTimeT(fileModWhen) > entry->getModWhen()) {
                     // mark as deleted so add() will succeed
                     if (bundle->getVerbose()) {
                         printf("      (removing old '%s')\n", storageName.c_str());