Merge changes I4cc652fe,I03f43442 into main

* changes:
  PriorityNestedScrollConnection: make onStop suspendable and add onCancel
  Simplify PriorityNestedScrollConnection
diff --git a/cmds/bootanimation/OWNERS b/cmds/bootanimation/OWNERS
index b6fb007..2eda44d 100644
--- a/cmds/bootanimation/OWNERS
+++ b/cmds/bootanimation/OWNERS
@@ -1,3 +1,4 @@
 dupin@google.com
 shanh@google.com
 jreck@google.com
+rahulbanerjee@google.com
\ No newline at end of file
diff --git a/core/api/current.txt b/core/api/current.txt
index d74d13b..035f823 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -43851,8 +43851,8 @@
     field public static final String KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrq_thresholds_int_array";
     field public static final String KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY = "5g_nr_sssinr_thresholds_int_array";
     field public static final String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool";
-    field @FlaggedApi("com.android.internal.telephony.flags.show_call_id_and_call_waiting_in_additional_settings_menu") public static final String KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL = "additional_settings_caller_id_visibility_bool";
-    field @FlaggedApi("com.android.internal.telephony.flags.show_call_id_and_call_waiting_in_additional_settings_menu") public static final String KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL = "additional_settings_call_waiting_visibility_bool";
+    field public static final String KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL = "additional_settings_caller_id_visibility_bool";
+    field public static final String KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL = "additional_settings_call_waiting_visibility_bool";
     field public static final String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
     field public static final String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call";
     field public static final String KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL = "allow_emergency_numbers_in_call_log_bool";
@@ -43935,7 +43935,7 @@
     field public static final String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array";
     field public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
     field public static final String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
-    field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final String KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY = "cellular_service_capabilities_int_array";
+    field public static final String KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY = "cellular_service_capabilities_int_array";
     field public static final String KEY_CELLULAR_USAGE_SETTING_INT = "cellular_usage_setting_int";
     field public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL = "check_pricing_with_carrier_data_roaming_bool";
     field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool";
@@ -45973,7 +45973,7 @@
     method @Nullable public String getMncString();
     method @Deprecated public String getNumber();
     method public int getPortIndex();
-    method @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") @NonNull public java.util.Set<java.lang.Integer> getServiceCapabilities();
+    method @NonNull public java.util.Set<java.lang.Integer> getServiceCapabilities();
     method public int getSimSlotIndex();
     method public int getSubscriptionId();
     method public int getSubscriptionType();
@@ -46056,9 +46056,9 @@
     field public static final int PHONE_NUMBER_SOURCE_CARRIER = 2; // 0x2
     field public static final int PHONE_NUMBER_SOURCE_IMS = 3; // 0x3
     field public static final int PHONE_NUMBER_SOURCE_UICC = 1; // 0x1
-    field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final int SERVICE_CAPABILITY_DATA = 3; // 0x3
-    field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final int SERVICE_CAPABILITY_SMS = 2; // 0x2
-    field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final int SERVICE_CAPABILITY_VOICE = 1; // 0x1
+    field public static final int SERVICE_CAPABILITY_DATA = 3; // 0x3
+    field public static final int SERVICE_CAPABILITY_SMS = 2; // 0x2
+    field public static final int SERVICE_CAPABILITY_VOICE = 1; // 0x1
     field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0
     field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1
     field public static final int USAGE_SETTING_DATA_CENTRIC = 2; // 0x2
@@ -46307,8 +46307,8 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataEnabled();
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataEnabledForReason(int);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataRoamingEnabled();
-    method @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public boolean isDeviceSmsCapable();
-    method @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public boolean isDeviceVoiceCapable();
+    method public boolean isDeviceSmsCapable();
+    method public boolean isDeviceVoiceCapable();
     method public boolean isEmergencyNumber(@NonNull String);
     method public boolean isHearingAidCompatibilitySupported();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isManualNetworkSelectionAllowed();
@@ -50947,6 +50947,7 @@
     method public android.view.Display.Mode[] getSupportedModes();
     method @Deprecated public float[] getSupportedRefreshRates();
     method @Deprecated public int getWidth();
+    method @FlaggedApi("com.android.server.display.feature.flags.enable_has_arr_support") public boolean hasArrSupport();
     method public boolean isHdr();
     method public boolean isHdrSdrRatioAvailable();
     method public boolean isMinimalPostProcessingSupported();
@@ -61687,6 +61688,8 @@
 
   public final class BackEvent {
     ctor public BackEvent(float, float, float, int);
+    ctor @FlaggedApi("com.android.window.flags.predictive_back_timestamp_api") public BackEvent(float, float, float, int, long);
+    method @FlaggedApi("com.android.window.flags.predictive_back_timestamp_api") public long getFrameTime();
     method @FloatRange(from=0, to=1) public float getProgress();
     method public int getSwipeEdge();
     method public float getTouchX();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 49b711b..fa4fc43 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -7319,6 +7319,7 @@
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_APP_OPS = 8; // 0x8
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_CLIENT_VOLUME = 16; // 0x10
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_MASTER = 1; // 0x1
+    field @FlaggedApi("android.media.audio.muted_by_port_volume_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_PORT_VOLUME = 64; // 0x40
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_MUTED = 4; // 0x4
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_VOLUME = 2; // 0x2
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_VOLUME_SHAPER = 32; // 0x20
@@ -13104,7 +13105,7 @@
     method public final void adjustNotification(@NonNull android.service.notification.Adjustment);
     method public final void adjustNotifications(@NonNull java.util.List<android.service.notification.Adjustment>);
     method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
-    method @Deprecated public void onAllowedAdjustmentsChanged();
+    method public void onAllowedAdjustmentsChanged();
     method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
     method public void onNotificationClicked(@NonNull String);
     method public void onNotificationDirectReplied(@NonNull String);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 5e4485c..1ff8c51 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -393,7 +393,9 @@
 
   public class NotificationManager {
     method @FlaggedApi("android.app.modes_api") @NonNull public String addAutomaticZenRule(@NonNull android.app.AutomaticZenRule, boolean);
+    method @FlaggedApi("android.service.notification.notification_classification") public void allowAssistantAdjustment(@NonNull String);
     method public void cleanUpCallersAfter(long);
+    method @FlaggedApi("android.service.notification.notification_classification") public void disallowAssistantAdjustment(@NonNull String);
     method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy getDefaultZenPolicy();
     method public android.content.ComponentName getEffectsSuppressor();
     method @FlaggedApi("android.service.notification.notification_classification") @NonNull public java.util.Set<java.lang.String> getUnsupportedAdjustmentTypes();
@@ -1099,6 +1101,7 @@
     field public static final long OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT = 265452344L; // 0xfd27b38L
     field public static final long OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION = 255940284L; // 0xf4156bcL
     field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
+    field public static final long UNIVERSAL_RESIZABLE_BY_DEFAULT = 357141415L; // 0x15498ba7L
   }
 
   public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 0c02ba4..99625ac 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3841,6 +3841,18 @@
         return activityRecord != null ? activityRecord.activity : null;
     }
 
+    /**
+     * Returns the most recent created activity that's still running.
+     */
+    @Nullable
+    public Activity getLastCreatedActivity() {
+        if (mActivities.isEmpty()) {
+            return null;
+        }
+
+        return mActivities.valueAt(mActivities.size() - 1).activity;
+    }
+
     @Override
     public ActivityClientRecord getActivityClient(IBinder token) {
         return mActivities.get(token);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 3b2aab4..a7597b4 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -84,6 +84,8 @@
     boolean isImportanceLocked(String pkg, int uid);
 
     List<String> getAllowedAssistantAdjustments(String pkg);
+    void allowAssistantAdjustment(String adjustmentType);
+    void disallowAssistantAdjustment(String adjustmentType);
 
     boolean shouldHideSilentStatusIcons(String callingPkg);
     void setHideSilentStatusIcons(boolean hide);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8b33417..4ef5b51 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -11296,7 +11296,11 @@
          * @see Segment
          */
         public @NonNull ProgressStyle setProgressSegments(@NonNull List<Segment> progressSegments) {
-            mProgressSegments = new ArrayList<>(progressSegments.size());
+            if (mProgressSegments == null) {
+                mProgressSegments = new ArrayList<>();
+            }
+            mProgressSegments.clear();
+            mProgressSegments.addAll(progressSegments);
             return this;
         }
 
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 41abd68..06bf67c 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1790,6 +1790,34 @@
         }
     }
 
+    /**
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public void allowAssistantAdjustment(@NonNull String capability) {
+        INotificationManager service = getService();
+        try {
+            service.allowAssistantAdjustment(capability);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public void disallowAssistantAdjustment(@NonNull String capability) {
+        INotificationManager service = getService();
+        try {
+            service.disallowAssistantAdjustment(capability);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /** @hide */
     @TestApi
     public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String pkg) {
diff --git a/core/java/android/app/contextualsearch/ContextualSearchManager.java b/core/java/android/app/contextualsearch/ContextualSearchManager.java
index cfbe741..3438cc8 100644
--- a/core/java/android/app/contextualsearch/ContextualSearchManager.java
+++ b/core/java/android/app/contextualsearch/ContextualSearchManager.java
@@ -102,6 +102,7 @@
      * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
      */
     public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN";
+
     /**
      * 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.
@@ -111,6 +112,14 @@
     public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH =
             "android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH";
 
+    /**
+     * System feature declaring that the device supports Contextual Search.
+     *
+     * @hide
+     */
+    public static final String FEATURE_CONTEXTUAL_SEARCH =
+            "com.google.android.feature.CONTEXTUAL_SEARCH";
+
     /** Entrypoint to be used when a user long presses on the nav handle. */
     public static final int ENTRYPOINT_LONG_PRESS_NAV_HANDLE = 1;
     /** Entrypoint to be used when a user long presses on the home button. */
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 8014537..11e8850 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -48,6 +48,16 @@
 }
 
 flag {
+  name: "modes_hsum"
+  namespace: "systemui"
+  description: "Fixes for modes (and DND/Zen in general) with HSUM or secondary users"
+  bug: "366203070"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "api_tvextender"
   is_exported: true
   namespace: "systemui"
@@ -262,3 +272,10 @@
   description: "[RONs] Guards new promotion logic and UI, including AOD notification and Colorization"
   bug: "367705002"
 }
+
+flag {
+  name: "backup_restore_logging"
+  namespace: "systemui"
+  description: "Adds logging for notification/modes backup and restore events"
+  bug: "289524803"
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 481e6b5..ce52825 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -21,10 +21,12 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.app.Activity;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.Disabled;
+import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.Overridable;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -1204,6 +1206,18 @@
     public @interface SizeChangesSupportMode {}
 
     /**
+     * This change id makes the restriction of fixed orientation, aspect ratio, and resizability
+     * of the app to be ignored, which means making the app fill the given available area.
+     * @hide
+     */
+    @ChangeId
+    @Overridable
+    @TestApi
+    @SuppressLint("UnflaggedApi") // @TestApi without associated public API.
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public static final long UNIVERSAL_RESIZABLE_BY_DEFAULT = 357141415L; // buganizer id
+
+    /**
      * This change id enables compat policy that ignores app requested orientation in
      * response to an app calling {@link android.app.Activity#setRequestedOrientation}. See
      * com.android.server.wm.LetterboxUiController#shouldIgnoreRequestedOrientation for
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index dcb363c..efddd1f 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -21,38 +21,4 @@
     namespace: "vcn"
     description: "Feature flag for enabling network metric monitor"
     bug: "282996138"
-}
-
-flag{
-    name: "validate_network_on_ipsec_loss"
-    namespace: "vcn"
-    description: "Trigger network validation when IPsec packet loss exceeds the threshold"
-    bug: "329139898"
-}
-
-flag{
-    name: "evaluate_ipsec_loss_on_lp_nc_change"
-    namespace: "vcn"
-    description: "Re-evaluate IPsec packet loss on LinkProperties or NetworkCapabilities change"
-    bug: "323238888"
-}
-
-flag{
-    name: "enforce_main_user"
-    namespace: "vcn"
-    description: "Enforce main user to make VCN HSUM compatible"
-    bug: "310310661"
-    metadata {
-      purpose: PURPOSE_BUGFIX
-    }
-}
-
-flag{
-    name: "handle_seq_num_leap"
-    namespace: "vcn"
-    description: "Do not report bad network when there is a suspected sequence number leap"
-    bug: "332598276"
-    metadata {
-      purpose: PURPOSE_BUGFIX
-    }
 }
\ No newline at end of file
diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index 37a91e7..b384b66 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -58,7 +58,6 @@
     void onSuggestedReplySent(String key, in CharSequence reply, int source);
     void onActionClicked(String key, in Notification.Action action, int source);
     void onNotificationClicked(String key);
-    // @deprecated changing allowed adjustments is no longer supported.
     void onAllowedAdjustmentsChanged();
     void onNotificationFeedbackReceived(String key, in NotificationRankingUpdate update, in Bundle feedback);
 }
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index 7b7058e..091b25a 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -319,10 +319,7 @@
      * their notifications the assistant can modify.
      * <p> Query {@link NotificationManager#getAllowedAssistantAdjustments()} to see what
      * {@link Adjustment adjustments} you are currently allowed to make.</p>
-     *
-     * @deprecated changing allowed adjustments is no longer supported.
      */
-    @Deprecated
     public void onAllowedAdjustmentsChanged() {
     }
 
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 8a8022c..e940e55bd 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -21,6 +21,7 @@
 import static android.hardware.flags.Flags.FLAG_OVERLAYPROPERTIES_CLASS_API;
 
 import static com.android.server.display.feature.flags.Flags.FLAG_HIGHEST_HDR_SDR_RATIO_API;
+import static com.android.server.display.feature.flags.Flags.FLAG_ENABLE_HAS_ARR_SUPPORT;
 
 import android.Manifest;
 import android.annotation.FlaggedApi;
@@ -1266,6 +1267,18 @@
     }
 
     /**
+     * Returns whether display supports adaptive refresh rate or not.
+     */
+    // TODO(b/372526856) Add a link to the documentation for ARR.
+    @FlaggedApi(FLAG_ENABLE_HAS_ARR_SUPPORT)
+    public boolean hasArrSupport() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.hasArrSupport;
+        }
+    }
+
+    /**
      * <p> Returns true if the connected display can be switched into a mode with minimal
      * post processing. </p>
      *
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index cac3e3c..26fce90 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -198,6 +198,12 @@
     public float renderFrameRate;
 
     /**
+     * If {@code true} this Display supports adaptive refresh rates.
+     * // TODO(b/372526856) Add a link to the documentation for ARR.
+     */
+    public boolean hasArrSupport;
+
+    /**
      * The default display mode.
      */
     public int defaultModeId;
@@ -436,6 +442,7 @@
                 && rotation == other.rotation
                 && modeId == other.modeId
                 && renderFrameRate == other.renderFrameRate
+                && hasArrSupport == other.hasArrSupport
                 && defaultModeId == other.defaultModeId
                 && userPreferredModeId == other.userPreferredModeId
                 && Arrays.equals(supportedModes, other.supportedModes)
@@ -497,6 +504,7 @@
         rotation = other.rotation;
         modeId = other.modeId;
         renderFrameRate = other.renderFrameRate;
+        hasArrSupport = other.hasArrSupport;
         defaultModeId = other.defaultModeId;
         userPreferredModeId = other.userPreferredModeId;
         supportedModes = Arrays.copyOf(other.supportedModes, other.supportedModes.length);
@@ -553,6 +561,7 @@
         rotation = source.readInt();
         modeId = source.readInt();
         renderFrameRate = source.readFloat();
+        hasArrSupport = source.readBoolean();
         defaultModeId = source.readInt();
         userPreferredModeId = source.readInt();
         int nModes = source.readInt();
@@ -626,6 +635,7 @@
         dest.writeInt(rotation);
         dest.writeInt(modeId);
         dest.writeFloat(renderFrameRate);
+        dest.writeBoolean(hasArrSupport);
         dest.writeInt(defaultModeId);
         dest.writeInt(userPreferredModeId);
         dest.writeInt(supportedModes.length);
@@ -871,6 +881,8 @@
         sb.append(modeId);
         sb.append(", renderFrameRate ");
         sb.append(renderFrameRate);
+        sb.append(", hasArrSupport ");
+        sb.append(hasArrSupport);
         sb.append(", defaultMode ");
         sb.append(defaultModeId);
         sb.append(", userPreferredModeId ");
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 19e244a..e6de478 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -1810,6 +1810,7 @@
         public DisplayMode[] supportedDisplayModes;
         public int activeDisplayModeId;
         public float renderFrameRate;
+        public boolean hasArrSupport;
 
         public int[] supportedColorModes;
         public int activeColorMode;
@@ -1827,6 +1828,7 @@
                     + "supportedDisplayModes=" + Arrays.toString(supportedDisplayModes)
                     + ", activeDisplayModeId=" + activeDisplayModeId
                     + ", renderFrameRate=" + renderFrameRate
+                    + ", hasArrSupport=" + hasArrSupport
                     + ", supportedColorModes=" + Arrays.toString(supportedColorModes)
                     + ", activeColorMode=" + activeColorMode
                     + ", hdrCapabilities=" + hdrCapabilities
@@ -1846,13 +1848,14 @@
                 && Arrays.equals(supportedColorModes, that.supportedColorModes)
                 && activeColorMode == that.activeColorMode
                 && Objects.equals(hdrCapabilities, that.hdrCapabilities)
-                && preferredBootDisplayMode == that.preferredBootDisplayMode;
+                && preferredBootDisplayMode == that.preferredBootDisplayMode
+                && hasArrSupport == that.hasArrSupport;
         }
 
         @Override
         public int hashCode() {
             return Objects.hash(Arrays.hashCode(supportedDisplayModes), activeDisplayModeId,
-                    renderFrameRate, activeColorMode, hdrCapabilities);
+                    renderFrameRate, activeColorMode, hdrCapabilities, hasArrSupport);
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b921213..3be9a82 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6971,9 +6971,7 @@
                     handleScrollCaptureRequest((IScrollCaptureResponseListener) msg.obj);
                     break;
                 case MSG_PAUSED_FOR_SYNC_TIMEOUT:
-                    Log.e(mTag, "Timedout waiting to unpause for sync");
-                    mNumPausedForSync = 0;
-                    scheduleTraversals();
+                    resumeAfterSyncTimeout();
                     break;
                 case MSG_CHECK_INVALIDATION_IDLE: {
                     long delta;
@@ -12777,6 +12775,15 @@
         activeSurfaceSyncGroup.addTransaction(t);
     }
 
+    /**
+     * Resume rendering after being paused for sync due to a timeout.
+     */
+    private void resumeAfterSyncTimeout() {
+        Log.e(mTag, "Timedout waiting to unpause for sync mNumPausedForSync=" + mNumPausedForSync);
+        mNumPausedForSync = 0;
+        scheduleTraversals();
+    }
+
     @Override
     public SurfaceSyncGroup getOrCreateSurfaceSyncGroup() {
         boolean newSyncGroup = false;
@@ -12804,6 +12811,16 @@
                 }
             });
             newSyncGroup = true;
+
+            // If the sync group is marked ready by a timeout, check if rendering is paused and
+            // if it is, resume rendering and trigger a traversal.
+            mActiveSurfaceSyncGroup.addSyncCompleteCallback(mExecutor, () -> {
+                if (mActiveSurfaceSyncGroup != null
+                        && mActiveSurfaceSyncGroup.isComplete() && mNumPausedForSync > 0) {
+                    mHandler.removeMessages(MSG_PAUSED_FOR_SYNC_TIMEOUT);
+                    resumeAfterSyncTimeout();
+                }
+            });
         }
 
         Trace.instant(Trace.TRACE_TAG_VIEW,
@@ -12818,12 +12835,20 @@
             }
         }
 
-        mNumPausedForSync++;
-        mHandler.removeMessages(MSG_PAUSED_FOR_SYNC_TIMEOUT);
-        mHandler.sendEmptyMessageDelayed(MSG_PAUSED_FOR_SYNC_TIMEOUT,
-                1000 * Build.HW_TIMEOUT_MULTIPLIER);
+        // The sync group can be marked ready by a timeout. This makes incrementing
+        // mNumPausedForSync racy. Here we check if the sync group is complete and
+        // if it is then we don't pause for syncing.
+        if (!mActiveSurfaceSyncGroup.isComplete()) {
+            mNumPausedForSync++;
+            mHandler.removeMessages(MSG_PAUSED_FOR_SYNC_TIMEOUT);
+            mHandler.sendEmptyMessageDelayed(MSG_PAUSED_FOR_SYNC_TIMEOUT,
+                    1000 * Build.HW_TIMEOUT_MULTIPLIER);
+        } else {
+            Log.d(mTag, "Active sync group is already completed "
+                    + mActiveSurfaceSyncGroup.getName());
+        }
         return mActiveSurfaceSyncGroup;
-    };
+    }
 
     private final Executor mSimpleExecutor = Runnable::run;
 
diff --git a/core/java/android/widget/RemoteCollectionItemsAdapter.java b/core/java/android/widget/RemoteCollectionItemsAdapter.java
index 9b396ae..fc09f88 100644
--- a/core/java/android/widget/RemoteCollectionItemsAdapter.java
+++ b/core/java/android/widget/RemoteCollectionItemsAdapter.java
@@ -40,13 +40,15 @@
     private RemoteCollectionItems mItems;
     private InteractionHandler mInteractionHandler;
     private ColorResources mColorResources;
+    private boolean mOnLightBackground;
 
     private SparseIntArray mLayoutIdToViewType;
 
     RemoteCollectionItemsAdapter(
             @NonNull RemoteCollectionItems items,
             @NonNull InteractionHandler interactionHandler,
-            @NonNull ColorResources colorResources) {
+            @NonNull ColorResources colorResources,
+            boolean onLightBackground) {
         // View type count can never increase after an adapter has been set on a ListView.
         // Additionally, decreasing it could inhibit view recycling if the count were to back and
         // forth between 3-2-3-2 for example. Therefore, the view type count, should be fixed for
@@ -56,6 +58,7 @@
         mItems = items;
         mInteractionHandler = interactionHandler;
         mColorResources = colorResources;
+        mOnLightBackground = onLightBackground;
 
         initLayoutIdToViewType();
     }
@@ -68,7 +71,8 @@
     void setData(
             @NonNull RemoteCollectionItems items,
             @NonNull InteractionHandler interactionHandler,
-            @NonNull ColorResources colorResources) {
+            @NonNull ColorResources colorResources,
+            boolean onLightBackground) {
         if (mViewTypeCount < items.getViewTypeCount()) {
             throw new IllegalArgumentException(
                     "RemoteCollectionItemsAdapter cannot increase view type count after creation");
@@ -77,6 +81,7 @@
         mItems = items;
         mInteractionHandler = interactionHandler;
         mColorResources = colorResources;
+        mOnLightBackground = onLightBackground;
 
         initLayoutIdToViewType();
 
@@ -184,6 +189,7 @@
                 : new AppWidgetHostView.AdapterChildHostView(parent.getContext());
         newView.setInteractionHandler(mInteractionHandler);
         newView.setColorResourcesNoReapply(mColorResources);
+        newView.setOnLightBackground(mOnLightBackground);
         newView.updateAppWidget(item);
         return newView;
     }
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index d7b5211..9b6311f 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1248,6 +1248,7 @@
 
             AdapterView adapterView = (AdapterView) target;
             Adapter adapter = adapterView.getAdapter();
+            boolean onLightBackground = hasFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT);
             // We can reuse the adapter if it's a RemoteCollectionItemsAdapter and the view type
             // count hasn't increased. Note that AbsListView allocates a fixed size array for view
             // recycling in setAdapter, so we must call setAdapter again if the number of view types
@@ -1255,8 +1256,12 @@
             if (adapter instanceof RemoteCollectionItemsAdapter
                     && adapter.getViewTypeCount() >= items.getViewTypeCount()) {
                 try {
-                    ((RemoteCollectionItemsAdapter) adapter).setData(
-                            items, params.handler, params.colorResources);
+                    ((RemoteCollectionItemsAdapter) adapter)
+                            .setData(
+                                    items,
+                                    params.handler,
+                                    params.colorResources,
+                                    onLightBackground);
                 } catch (Throwable throwable) {
                     // setData should never failed with the validation in the items builder, but if
                     // it does, catch and rethrow.
@@ -1266,8 +1271,9 @@
             }
 
             try {
-                adapterView.setAdapter(new RemoteCollectionItemsAdapter(items,
-                        params.handler, params.colorResources));
+                adapterView.setAdapter(
+                        new RemoteCollectionItemsAdapter(
+                                items, params.handler, params.colorResources, onLightBackground));
             } catch (Throwable throwable) {
                 // This could throw if the AdapterView somehow doesn't accept BaseAdapter due to
                 // a type error.
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index d3733b7..23e572f 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -16,8 +16,13 @@
 
 package android.window;
 
+import static com.android.window.flags.Flags.FLAG_PREDICTIVE_BACK_TIMESTAMP_API;
+import static com.android.window.flags.Flags.predictiveBackTimestampApi;
+
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
+import android.util.TimeUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -44,18 +49,25 @@
     private final float mTouchX;
     private final float mTouchY;
     private final float mProgress;
+    private final long mFrameTime;
 
     @SwipeEdge
     private final int mSwipeEdge;
 
     /** @hide */
     public static BackEvent fromBackMotionEvent(BackMotionEvent backMotionEvent) {
-        return new BackEvent(backMotionEvent.getTouchX(), backMotionEvent.getTouchY(),
-                backMotionEvent.getProgress(), backMotionEvent.getSwipeEdge());
+        if (predictiveBackTimestampApi()) {
+            return new BackEvent(backMotionEvent.getTouchX(), backMotionEvent.getTouchY(),
+                    backMotionEvent.getProgress(), backMotionEvent.getSwipeEdge(),
+                    backMotionEvent.getFrameTime());
+        } else {
+            return new BackEvent(backMotionEvent.getTouchX(), backMotionEvent.getTouchY(),
+                    backMotionEvent.getProgress(), backMotionEvent.getSwipeEdge());
+        }
     }
 
     /**
-     * Creates a new {@link BackEvent} instance.
+     * Creates a new {@link BackEvent} instance with the current uptime as frame time.
      *
      * @param touchX Absolute X location of the touch point of this event.
      * @param touchY Absolute Y location of the touch point of this event.
@@ -67,6 +79,26 @@
         mTouchY = touchY;
         mProgress = progress;
         mSwipeEdge = swipeEdge;
+        mFrameTime = System.nanoTime() / TimeUtils.NANOS_PER_MS;
+    }
+
+    /**
+     * Creates a new {@link BackEvent} instance.
+     *
+     * @param touchX Absolute X location of the touch point of this event.
+     * @param touchY Absolute Y location of the touch point of this event.
+     * @param progress Value between 0 and 1 on how far along the back gesture is.
+     * @param swipeEdge Indicates which edge the swipe starts from.
+     * @param frameTime frame time of the back event.
+     */
+    @FlaggedApi(FLAG_PREDICTIVE_BACK_TIMESTAMP_API)
+    public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge,
+            long frameTime) {
+        mTouchX = touchX;
+        mTouchY = touchY;
+        mProgress = progress;
+        mSwipeEdge = swipeEdge;
+        mFrameTime = frameTime;
     }
 
     /**
@@ -115,13 +147,38 @@
         return mSwipeEdge;
     }
 
+    /**
+     * Returns the frameTime of the BackEvent in milliseconds. Useful for calculating velocity.
+     */
+    @FlaggedApi(FLAG_PREDICTIVE_BACK_TIMESTAMP_API)
+    public long getFrameTime() {
+        return mFrameTime;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (!(other instanceof BackEvent)) {
+            return false;
+        }
+        final BackEvent that = (BackEvent) other;
+        return mTouchX == that.mTouchX
+                && mTouchY == that.mTouchY
+                && mProgress == that.mProgress
+                && mSwipeEdge == that.mSwipeEdge
+                && mFrameTime == that.mFrameTime;
+    }
+
     @Override
     public String toString() {
         return "BackEvent{"
                 + "mTouchX=" + mTouchX
                 + ", mTouchY=" + mTouchY
                 + ", mProgress=" + mProgress
-                + ", mSwipeEdge" + mSwipeEdge
+                + ", mSwipeEdge=" + mSwipeEdge
+                + ", mFrameTime=" + mFrameTime + "ms"
                 + "}";
     }
 }
diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java
index 8ac68ab..a8ec4ee 100644
--- a/core/java/android/window/BackMotionEvent.java
+++ b/core/java/android/window/BackMotionEvent.java
@@ -33,6 +33,7 @@
 public final class BackMotionEvent implements Parcelable {
     private final float mTouchX;
     private final float mTouchY;
+    private final long mFrameTime;
     private final float mProgress;
     private final boolean mTriggerBack;
 
@@ -48,6 +49,7 @@
      *
      * @param touchX Absolute X location of the touch point of this event.
      * @param touchY Absolute Y location of the touch point of this event.
+     * @param frameTime Event time of the corresponding touch event.
      * @param progress Value between 0 and 1 on how far along the back gesture is.
      * @param triggerBack Indicates whether the back arrow is in the triggered state or not
      * @param swipeEdge Indicates which edge the swipe starts from.
@@ -57,12 +59,14 @@
     public BackMotionEvent(
             float touchX,
             float touchY,
+            long frameTime,
             float progress,
             boolean triggerBack,
             @BackEvent.SwipeEdge int swipeEdge,
             @Nullable RemoteAnimationTarget departingAnimationTarget) {
         mTouchX = touchX;
         mTouchY = touchY;
+        mFrameTime = frameTime;
         mProgress = progress;
         mTriggerBack = triggerBack;
         mSwipeEdge = swipeEdge;
@@ -76,6 +80,7 @@
         mTriggerBack = in.readBoolean();
         mSwipeEdge = in.readInt();
         mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
+        mFrameTime = in.readLong();
     }
 
     @NonNull
@@ -104,6 +109,7 @@
         dest.writeBoolean(mTriggerBack);
         dest.writeInt(mSwipeEdge);
         dest.writeTypedObject(mDepartingAnimationTarget, flags);
+        dest.writeLong(mFrameTime);
     }
 
     /**
@@ -148,6 +154,13 @@
     }
 
     /**
+     * Returns the frame time of the BackMotionEvent in milliseconds.
+     */
+    public long getFrameTime() {
+        return mFrameTime;
+    }
+
+    /**
      * Returns the {@link RemoteAnimationTarget} of the top departing application window,
      * or {@code null} if the top window should not be moved for the current type of back
      * destination.
@@ -162,10 +175,11 @@
         return "BackMotionEvent{"
                 + "mTouchX=" + mTouchX
                 + ", mTouchY=" + mTouchY
+                + ", mFrameTime=" + mFrameTime + "ms"
                 + ", mProgress=" + mProgress
                 + ", mTriggerBack=" + mTriggerBack
-                + ", mSwipeEdge" + mSwipeEdge
-                + ", mDepartingAnimationTarget" + mDepartingAnimationTarget
+                + ", mSwipeEdge=" + mSwipeEdge
+                + ", mDepartingAnimationTarget=" + mDepartingAnimationTarget
                 + "}";
     }
 }
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index 465e11a..a5be58b 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -17,6 +17,7 @@
 package android.window;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.window.flags.Flags.predictiveBackTimestampApi;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -66,7 +67,8 @@
                 reset();
             };
     private final DynamicAnimation.OnAnimationUpdateListener mOnBackInvokedFlingUpdateListener =
-            (animation, progress, velocity) -> updateProgressValue(progress, velocity);
+            (animation, progress, velocity) ->
+                    updateProgressValue(progress, velocity, animation.getLastFrameTime());
 
 
     private void setProgress(float progress) {
@@ -92,7 +94,9 @@
 
     @Override
     public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) {
-        if (mBackInvokedFinishRunnable == null) updateProgressValue(value, velocity);
+        if (mBackInvokedFinishRunnable == null) {
+            updateProgressValue(value, velocity, animation.getLastFrameTime());
+        }
     }
 
 
@@ -137,7 +141,9 @@
         mLastBackEvent = event;
         mCallback = callback;
         mBackAnimationInProgress = true;
-        updateProgressValue(0, 0);
+        updateProgressValue(/* progress */ 0, /* velocity */ 0,
+                /* frameTime */ System.nanoTime() / TimeUtils.NANOS_PER_MS);
+        onBackProgressed(event);
     }
 
     /**
@@ -146,7 +152,8 @@
     public void reset() {
         if (mBackCancelledFinishRunnable != null) {
             // Ensure that last progress value that apps see is 0
-            updateProgressValue(0, 0);
+            updateProgressValue(/* progress */ 0, /* velocity */ 0,
+                    /* frameTime */ System.nanoTime() / TimeUtils.NANOS_PER_MS);
             invokeBackCancelledRunnable();
         } else if (mBackInvokedFinishRunnable != null) {
             invokeBackInvokedRunnable();
@@ -236,14 +243,20 @@
         return mVelocity / SCALE_FACTOR;
     }
 
-    private void updateProgressValue(float progress, float velocity) {
+    private void updateProgressValue(float progress, float velocity, long frameTime) {
         mVelocity = velocity;
         if (mLastBackEvent == null || mCallback == null || !mBackAnimationInProgress) {
             return;
         }
-        mCallback.onProgressUpdate(
-                new BackEvent(mLastBackEvent.getTouchX(), mLastBackEvent.getTouchY(),
-                        progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge()));
+        BackEvent backEvent;
+        if (predictiveBackTimestampApi()) {
+            backEvent = new BackEvent(mLastBackEvent.getTouchX(), mLastBackEvent.getTouchY(),
+                    progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge(), frameTime);
+        } else {
+            backEvent = new BackEvent(mLastBackEvent.getTouchX(), mLastBackEvent.getTouchY(),
+                    progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge());
+        }
+        mCallback.onProgressUpdate(backEvent);
     }
 
     private void invokeBackCancelledRunnable() {
diff --git a/core/java/android/window/BackTouchTracker.java b/core/java/android/window/BackTouchTracker.java
index 290c494..39a3025 100644
--- a/core/java/android/window/BackTouchTracker.java
+++ b/core/java/android/window/BackTouchTracker.java
@@ -151,6 +151,7 @@
         return new BackMotionEvent(
                 /* touchX = */ mInitTouchX,
                 /* touchY = */ mInitTouchY,
+                /* frameTime = */ 0,
                 /* progress = */ 0,
                 /* triggerBack = */ mTriggerBack,
                 /* swipeEdge = */ mSwipeEdge,
@@ -235,6 +236,7 @@
         return new BackMotionEvent(
                 /* touchX = */ mLatestTouchX,
                 /* touchY = */ mLatestTouchY,
+                /* frameTime = */ 0,
                 /* progress = */ progress,
                 /* triggerBack = */ mTriggerBack,
                 /* swipeEdge = */ mSwipeEdge,
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index 66c35e2..8db1f95 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -17,6 +17,7 @@
 package android.window;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.window.flags.Flags.predictiveBackTimestampApi;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -235,8 +236,12 @@
         @Override
         public void onBackStarted(@NonNull BackEvent backEvent) {
             try {
+                long frameTime = 0;
+                if (predictiveBackTimestampApi()) {
+                    frameTime = backEvent.getFrameTime();
+                }
                 mIOnBackInvokedCallback.onBackStarted(
-                        new BackMotionEvent(backEvent.getTouchX(), backEvent.getTouchY(),
+                        new BackMotionEvent(backEvent.getTouchX(), backEvent.getTouchY(), frameTime,
                                 backEvent.getProgress(), false, backEvent.getSwipeEdge(),
                                 null));
             } catch (RemoteException e) {
@@ -247,8 +252,12 @@
         @Override
         public void onBackProgressed(@NonNull BackEvent backEvent) {
             try {
+                long frameTime = 0;
+                if (predictiveBackTimestampApi()) {
+                    frameTime = backEvent.getFrameTime();
+                }
                 mIOnBackInvokedCallback.onBackProgressed(
-                        new BackMotionEvent(backEvent.getTouchX(), backEvent.getTouchY(),
+                        new BackMotionEvent(backEvent.getTouchX(), backEvent.getTouchY(), frameTime,
                                 backEvent.getProgress(), false, backEvent.getSwipeEdge(),
                                 null));
             } catch (RemoteException e) {
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 5d14698..a68bdc0 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -839,6 +839,16 @@
     }
 
     /**
+     * Returns true if the SurfaceSyncGroup has completed its sync.
+     * @hide
+     */
+    public boolean isComplete() {
+        synchronized (mLock) {
+            return mFinished;
+        }
+    }
+
+    /**
      * A frame callback that is used to synchronize SurfaceViews. The owner of the SurfaceView must
      * implement onFrameStarted when trying to sync the SurfaceView. This is to ensure the sync
      * knows when the frame is ready to add to the sync.
diff --git a/core/java/android/window/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java
index 20d1b3b..a37bef8 100644
--- a/core/java/android/window/TaskSnapshot.java
+++ b/core/java/android/window/TaskSnapshot.java
@@ -83,13 +83,16 @@
     public static final int REFERENCE_CACHE = 1 << 1;
     /** This snapshot object is being persistent. */
     public static final int REFERENCE_PERSIST = 1 << 2;
+    /** This snapshot object is being used for content suggestion. */
+    public static final int REFERENCE_CONTENT_SUGGESTION = 1 << 3;
     @IntDef(flag = true, prefix = { "REFERENCE_" }, value = {
             REFERENCE_BROADCAST,
             REFERENCE_CACHE,
-            REFERENCE_PERSIST
+            REFERENCE_PERSIST,
+            REFERENCE_CONTENT_SUGGESTION
     })
     @Retention(RetentionPolicy.SOURCE)
-    @interface ReferenceFlags {}
+    public @interface ReferenceFlags {}
 
     public TaskSnapshot(long id, long captureTime,
             @NonNull ComponentName topActivityComponent, HardwareBuffer snapshot,
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 44a374f..c9d458f 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -17,6 +17,7 @@
 package android.window;
 
 import static com.android.window.flags.Flags.predictiveBackPrioritySystemNavigationObserver;
+import static com.android.window.flags.Flags.predictiveBackTimestampApi;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -563,7 +564,8 @@
                 }
                 OnBackAnimationCallback animationCallback = getBackAnimationCallback();
                 if (animationCallback != null
-                        && !(callback instanceof ImeBackAnimationController)) {
+                        && !(callback instanceof ImeBackAnimationController)
+                        && !predictiveBackTimestampApi()) {
                     mProgressAnimator.onBackInvoked(() -> {
                         if (mIsSystemCallback) {
                             mSystemNavigationObserverCallbackRunnable.run();
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 f739622..235ba3a 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
@@ -37,6 +37,13 @@
 }
 
 flag {
+  name: "move_to_external_display_shortcut"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Whether the move to external display shortcut in overview is available"
+  bug: "372872848"
+}
+
+flag {
   name: "app_compat_properties_api"
   is_exported: true
   namespace: "large_screen_experiences_app_compat"
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 622f8c8..0d235ff 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -70,17 +70,6 @@
 }
 
 flag {
-  name: "common_surface_animator"
-  namespace: "windowing_frontend"
-  description: "A reusable surface animator for default transition"
-  bug: "326331384"
-  is_fixed_read_only: true
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
   name: "reduce_keyguard_transitions"
   namespace: "windowing_frontend"
   description: "Avoid setting keyguard transitions ready unless there are no other changes"
@@ -325,3 +314,11 @@
     is_fixed_read_only: true
     bug: "362938401"
 }
+
+flag {
+    name: "predictive_back_timestamp_api"
+    namespace: "systemui"
+    description: "expose timestamp in BackEvent (API extension)"
+    is_fixed_read_only: true
+    bug: "362938401"
+}
diff --git a/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java b/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java
index d4fe7c8d..7a4c1a0 100644
--- a/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java
+++ b/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java
@@ -762,6 +762,10 @@
         return mAnimationHandler != null ? mAnimationHandler : AnimationHandler.getInstance();
     }
 
+    public long getLastFrameTime() {
+        return mLastFrameTime;
+    }
+
     /****************Sub class animations**************/
     /**
      * Returns the acceleration at the given value with the given velocity.
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index f3dc896..a037cb4 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -218,6 +218,22 @@
         // NOTE: Registering that datasource is an async operation, so there may be no data traced
         // for some messages logged right after the construction of this class.
         mDataSource.register(params);
+
+        if (viewerConfigInputStreamProvider == null && viewerConfigFilePath != null) {
+            viewerConfigInputStreamProvider = new ViewerConfigInputStreamProvider() {
+                @NonNull
+                @Override
+                public ProtoInputStream getInputStream() {
+                    try {
+                        return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
+                    } catch (FileNotFoundException e) {
+                        throw new RuntimeException(
+                                "Failed to load viewer config file " + viewerConfigFilePath, e);
+                    }
+                }
+            };
+        }
+
         this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
         this.mViewerConfigReader = viewerConfigReader;
         this.mCacheUpdater = cacheUpdater;
diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index 0a80e00..dd6c879 100644
--- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -39,7 +39,7 @@
      * or the viewer config is not loaded into memory.
      */
     @Nullable
-    public synchronized String getViewerString(long messageHash) {
+    public String getViewerString(long messageHash) {
         return mLogMessageMap.get(messageHash);
     }
 
diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp
index 041fed7..68d49cd 100644
--- a/core/jni/android_hardware_camera2_CameraMetadata.cpp
+++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp
@@ -336,7 +336,7 @@
 static jbyteArray CameraMetadata_readValues(JNIEnv *env, jclass thiz, jint tag, jlong ptr) {
     ALOGV("%s (tag = %d)", __FUNCTION__, tag);
 
-    CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, ptr);
+    const CameraMetadata *metadata = CameraMetadata_getPointerThrow(env, ptr);
     if (metadata == NULL) return NULL;
 
     const camera_metadata_t *metaBuffer = metadata->getAndLock();
@@ -349,16 +349,14 @@
     }
     size_t tagSize = Helpers::getTypeSize(tagType);
 
-    camera_metadata_entry entry = metadata->find(tag);
+    camera_metadata_ro_entry entry = metadata->find(tag);
     if (entry.count == 0) {
-         if (!metadata->exists(tag)) {
-             ALOGV("%s: Tag %d does not have any entries", __FUNCTION__, tag);
-             return NULL;
-         } else {
-             // OK: we will return a 0-sized array.
-             ALOGV("%s: Tag %d had an entry, but it had 0 data", __FUNCTION__,
-                   tag);
-         }
+        if (!metadata->exists(tag)) {
+            ALOGV("%s: Tag %d does not have any entries", __FUNCTION__, tag);
+        } else {
+            ALOGV("%s: Tag %d had an entry, but it had 0 data", __FUNCTION__, tag);
+        }
+        return NULL;
     }
 
     jsize byteCount = entry.count * tagSize;
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 9bccf5a..8eaa7aa 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -747,16 +747,12 @@
                                           indexMax));
 }
 
-static jint
-android_media_AudioSystem_setStreamVolumeIndex(JNIEnv *env,
-                                               jobject thiz,
-                                               jint stream,
-                                               jint index,
-                                               jint device)
-{
+static jint android_media_AudioSystem_setStreamVolumeIndex(JNIEnv *env, jobject thiz, jint stream,
+                                                           jint index, jboolean muted,
+                                                           jint device) {
     return check_AudioSystem_Command(
             AudioSystem::setStreamVolumeIndex(static_cast<audio_stream_type_t>(stream), index,
-                                              static_cast<audio_devices_t>(device)));
+                                              muted, static_cast<audio_devices_t>(device)));
 }
 
 static jint
@@ -773,13 +769,9 @@
     return index;
 }
 
-static jint
-android_media_AudioSystem_setVolumeIndexForAttributes(JNIEnv *env,
-                                                      jobject thiz,
-                                                      jobject jaa,
-                                                      jint index,
-                                                      jint device)
-{
+static jint android_media_AudioSystem_setVolumeIndexForAttributes(JNIEnv *env, jobject thiz,
+                                                                  jobject jaa, jint index,
+                                                                  jboolean muted, jint device) {
     // read the AudioAttributes values
     JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
     jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
@@ -787,7 +779,7 @@
         return jStatus;
     }
     return check_AudioSystem_Command(
-            AudioSystem::setVolumeIndexForAttributes(*(paa.get()), index,
+            AudioSystem::setVolumeIndexForAttributes(*(paa.get()), index, muted,
                                                      static_cast<audio_devices_t>(device)));
 }
 
@@ -3448,182 +3440,179 @@
 #define MAKE_AUDIO_SYSTEM_METHOD(x) \
     MAKE_JNI_NATIVE_METHOD_AUTOSIG(#x, android_media_AudioSystem_##x)
 
-static const JNINativeMethod gMethods[] =
-        {MAKE_AUDIO_SYSTEM_METHOD(setParameters),
-         MAKE_AUDIO_SYSTEM_METHOD(getParameters),
-         MAKE_AUDIO_SYSTEM_METHOD(muteMicrophone),
-         MAKE_AUDIO_SYSTEM_METHOD(isMicrophoneMuted),
-         MAKE_AUDIO_SYSTEM_METHOD(isStreamActive),
-         MAKE_AUDIO_SYSTEM_METHOD(isStreamActiveRemotely),
-         MAKE_AUDIO_SYSTEM_METHOD(isSourceActive),
-         MAKE_AUDIO_SYSTEM_METHOD(newAudioSessionId),
-         MAKE_AUDIO_SYSTEM_METHOD(newAudioPlayerId),
-         MAKE_AUDIO_SYSTEM_METHOD(newAudioRecorderId),
-         MAKE_JNI_NATIVE_METHOD("setDeviceConnectionState", "(ILandroid/os/Parcel;I)I",
-                                android_media_AudioSystem_setDeviceConnectionState),
-         MAKE_AUDIO_SYSTEM_METHOD(getDeviceConnectionState),
-         MAKE_AUDIO_SYSTEM_METHOD(handleDeviceConfigChange),
-         MAKE_AUDIO_SYSTEM_METHOD(setPhoneState),
-         MAKE_AUDIO_SYSTEM_METHOD(setForceUse),
-         MAKE_AUDIO_SYSTEM_METHOD(getForceUse),
-         MAKE_AUDIO_SYSTEM_METHOD(setDeviceAbsoluteVolumeEnabled),
-         MAKE_AUDIO_SYSTEM_METHOD(initStreamVolume),
-         MAKE_AUDIO_SYSTEM_METHOD(setStreamVolumeIndex),
-         MAKE_AUDIO_SYSTEM_METHOD(getStreamVolumeIndex),
-         MAKE_JNI_NATIVE_METHOD("setVolumeIndexForAttributes",
-                                "(Landroid/media/AudioAttributes;II)I",
-                                android_media_AudioSystem_setVolumeIndexForAttributes),
-         MAKE_JNI_NATIVE_METHOD("getVolumeIndexForAttributes",
-                                "(Landroid/media/AudioAttributes;I)I",
-                                android_media_AudioSystem_getVolumeIndexForAttributes),
-         MAKE_JNI_NATIVE_METHOD("getMinVolumeIndexForAttributes",
-                                "(Landroid/media/AudioAttributes;)I",
-                                android_media_AudioSystem_getMinVolumeIndexForAttributes),
-         MAKE_JNI_NATIVE_METHOD("getMaxVolumeIndexForAttributes",
-                                "(Landroid/media/AudioAttributes;)I",
-                                android_media_AudioSystem_getMaxVolumeIndexForAttributes),
-         MAKE_AUDIO_SYSTEM_METHOD(setMasterVolume),
-         MAKE_AUDIO_SYSTEM_METHOD(getMasterVolume),
-         MAKE_AUDIO_SYSTEM_METHOD(setMasterMute),
-         MAKE_AUDIO_SYSTEM_METHOD(getMasterMute),
-         MAKE_AUDIO_SYSTEM_METHOD(setMasterMono),
-         MAKE_AUDIO_SYSTEM_METHOD(getMasterMono),
-         MAKE_AUDIO_SYSTEM_METHOD(setMasterBalance),
-         MAKE_AUDIO_SYSTEM_METHOD(getMasterBalance),
-         MAKE_AUDIO_SYSTEM_METHOD(getPrimaryOutputSamplingRate),
-         MAKE_AUDIO_SYSTEM_METHOD(getPrimaryOutputFrameCount),
-         MAKE_AUDIO_SYSTEM_METHOD(getOutputLatency),
-         MAKE_AUDIO_SYSTEM_METHOD(setLowRamDevice),
-         MAKE_AUDIO_SYSTEM_METHOD(checkAudioFlinger),
-         MAKE_JNI_NATIVE_METHOD("setAudioFlingerBinder", "(Landroid/os/IBinder;)V",
-                                android_media_AudioSystem_setAudioFlingerBinder),
-         MAKE_JNI_NATIVE_METHOD("listAudioPorts", "(Ljava/util/ArrayList;[I)I",
-                                android_media_AudioSystem_listAudioPorts),
-         MAKE_JNI_NATIVE_METHOD("getSupportedDeviceTypes", "(ILandroid/util/IntArray;)I",
-                                android_media_AudioSystem_getSupportedDeviceTypes),
-         MAKE_JNI_NATIVE_METHOD("createAudioPatch",
-                                "([Landroid/media/AudioPatch;[Landroid/media/"
-                                "AudioPortConfig;[Landroid/media/AudioPortConfig;)I",
-                                android_media_AudioSystem_createAudioPatch),
-         MAKE_JNI_NATIVE_METHOD("releaseAudioPatch", "(Landroid/media/AudioPatch;)I",
-                                android_media_AudioSystem_releaseAudioPatch),
-         MAKE_JNI_NATIVE_METHOD("listAudioPatches", "(Ljava/util/ArrayList;[I)I",
-                                android_media_AudioSystem_listAudioPatches),
-         MAKE_JNI_NATIVE_METHOD("setAudioPortConfig", "(Landroid/media/AudioPortConfig;)I",
-                                android_media_AudioSystem_setAudioPortConfig),
-         MAKE_JNI_NATIVE_METHOD("startAudioSource",
-                                "(Landroid/media/AudioPortConfig;Landroid/media/AudioAttributes;)I",
-                                android_media_AudioSystem_startAudioSource),
-         MAKE_AUDIO_SYSTEM_METHOD(stopAudioSource),
-         MAKE_AUDIO_SYSTEM_METHOD(getAudioHwSyncForSession),
-         MAKE_JNI_NATIVE_METHOD("registerPolicyMixes", "(Ljava/util/ArrayList;Z)I",
-                                android_media_AudioSystem_registerPolicyMixes),
-         MAKE_JNI_NATIVE_METHOD("getRegisteredPolicyMixes", "(Ljava/util/List;)I",
-                                android_media_AudioSystem_getRegisteredPolicyMixes),
-         MAKE_JNI_NATIVE_METHOD("updatePolicyMixes",
-                                "([Landroid/media/audiopolicy/AudioMix;[Landroid/media/audiopolicy/"
-                                "AudioMixingRule;)I",
-                                android_media_AudioSystem_updatePolicyMixes),
-         MAKE_JNI_NATIVE_METHOD("setUidDeviceAffinities", "(I[I[Ljava/lang/String;)I",
-                                android_media_AudioSystem_setUidDeviceAffinities),
-         MAKE_AUDIO_SYSTEM_METHOD(removeUidDeviceAffinities),
-         MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_dynamic_policy_callback",
-                                        android_media_AudioSystem_registerDynPolicyCallback),
-         MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_recording_callback",
-                                        android_media_AudioSystem_registerRecordingCallback),
-         MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_routing_callback",
-                                        android_media_AudioSystem_registerRoutingCallback),
-         MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_vol_range_init_req_callback",
-                                        android_media_AudioSystem_registerVolRangeInitReqCallback),
-         MAKE_AUDIO_SYSTEM_METHOD(systemReady),
-         MAKE_AUDIO_SYSTEM_METHOD(getStreamVolumeDB),
-         MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_get_offload_support",
-                                        android_media_AudioSystem_getOffloadSupport),
-         MAKE_JNI_NATIVE_METHOD("getMicrophones", "(Ljava/util/ArrayList;)I",
-                                android_media_AudioSystem_getMicrophones),
-         MAKE_JNI_NATIVE_METHOD("getSurroundFormats", "(Ljava/util/Map;)I",
-                                android_media_AudioSystem_getSurroundFormats),
-         MAKE_JNI_NATIVE_METHOD("getReportedSurroundFormats", "(Ljava/util/ArrayList;)I",
-                                android_media_AudioSystem_getReportedSurroundFormats),
-         MAKE_AUDIO_SYSTEM_METHOD(setSurroundFormatEnabled),
-         MAKE_AUDIO_SYSTEM_METHOD(setAssistantServicesUids),
-         MAKE_AUDIO_SYSTEM_METHOD(setActiveAssistantServicesUids),
-         MAKE_AUDIO_SYSTEM_METHOD(setA11yServicesUids),
-         MAKE_AUDIO_SYSTEM_METHOD(isHapticPlaybackSupported),
-         MAKE_AUDIO_SYSTEM_METHOD(isUltrasoundSupported),
-         MAKE_JNI_NATIVE_METHOD(
-                 "getHwOffloadFormatsSupportedForBluetoothMedia", "(ILjava/util/ArrayList;)I",
-                 android_media_AudioSystem_getHwOffloadFormatsSupportedForBluetoothMedia),
-         MAKE_AUDIO_SYSTEM_METHOD(setSupportedSystemUsages),
-         MAKE_AUDIO_SYSTEM_METHOD(setAllowedCapturePolicy),
-         MAKE_AUDIO_SYSTEM_METHOD(setRttEnabled),
-         MAKE_AUDIO_SYSTEM_METHOD(setAudioHalPids),
-         MAKE_AUDIO_SYSTEM_METHOD(isCallScreeningModeSupported),
-         MAKE_JNI_NATIVE_METHOD("setDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I",
-                                android_media_AudioSystem_setDevicesRoleForStrategy),
-         MAKE_JNI_NATIVE_METHOD("removeDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I",
-                                android_media_AudioSystem_removeDevicesRoleForStrategy),
-         MAKE_AUDIO_SYSTEM_METHOD(clearDevicesRoleForStrategy),
-         MAKE_JNI_NATIVE_METHOD("getDevicesForRoleAndStrategy", "(IILjava/util/List;)I",
-                                android_media_AudioSystem_getDevicesForRoleAndStrategy),
-         MAKE_JNI_NATIVE_METHOD("setDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
-                                android_media_AudioSystem_setDevicesRoleForCapturePreset),
-         MAKE_JNI_NATIVE_METHOD("addDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
-                                android_media_AudioSystem_addDevicesRoleForCapturePreset),
-         MAKE_JNI_NATIVE_METHOD("removeDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
-                                android_media_AudioSystem_removeDevicesRoleForCapturePreset),
-         MAKE_AUDIO_SYSTEM_METHOD(clearDevicesRoleForCapturePreset),
-         MAKE_JNI_NATIVE_METHOD("getDevicesForRoleAndCapturePreset", "(IILjava/util/List;)I",
-                                android_media_AudioSystem_getDevicesForRoleAndCapturePreset),
-         MAKE_JNI_NATIVE_METHOD("getDevicesForAttributes",
-                                "(Landroid/media/AudioAttributes;[Landroid/media/"
-                                "AudioDeviceAttributes;Z)I",
-                                android_media_AudioSystem_getDevicesForAttributes),
-         MAKE_JNI_NATIVE_METHOD("setUserIdDeviceAffinities", "(I[I[Ljava/lang/String;)I",
-                                android_media_AudioSystem_setUserIdDeviceAffinities),
-         MAKE_AUDIO_SYSTEM_METHOD(removeUserIdDeviceAffinities),
-         MAKE_AUDIO_SYSTEM_METHOD(setCurrentImeUid),
-         MAKE_JNI_NATIVE_METHOD("setVibratorInfos", "(Ljava/util/List;)I",
-                                android_media_AudioSystem_setVibratorInfos),
-         MAKE_JNI_NATIVE_METHOD("nativeGetSpatializer",
-                                "(Landroid/media/INativeSpatializerCallback;)Landroid/os/IBinder;",
-                                android_media_AudioSystem_getSpatializer),
-         MAKE_JNI_NATIVE_METHOD("canBeSpatialized",
-                                "(Landroid/media/AudioAttributes;Landroid/media/AudioFormat;"
-                                "[Landroid/media/AudioDeviceAttributes;)Z",
-                                android_media_AudioSystem_canBeSpatialized),
-         MAKE_JNI_NATIVE_METHOD("nativeGetSoundDose",
-                                "(Landroid/media/ISoundDoseCallback;)Landroid/os/IBinder;",
-                                android_media_AudioSystem_nativeGetSoundDose),
-         MAKE_JNI_NATIVE_METHOD("getDirectPlaybackSupport",
-                                "(Landroid/media/AudioFormat;Landroid/media/AudioAttributes;)I",
-                                android_media_AudioSystem_getDirectPlaybackSupport),
-         MAKE_JNI_NATIVE_METHOD("getDirectProfilesForAttributes",
-                                "(Landroid/media/AudioAttributes;Ljava/util/ArrayList;)I",
-                                android_media_AudioSystem_getDirectProfilesForAttributes),
-         MAKE_JNI_NATIVE_METHOD("getSupportedMixerAttributes", "(ILjava/util/List;)I",
-                                android_media_AudioSystem_getSupportedMixerAttributes),
-         MAKE_JNI_NATIVE_METHOD("setPreferredMixerAttributes",
-                                "(Landroid/media/AudioAttributes;IILandroid/media/"
-                                "AudioMixerAttributes;)I",
-                                android_media_AudioSystem_setPreferredMixerAttributes),
-         MAKE_JNI_NATIVE_METHOD("getPreferredMixerAttributes",
-                                "(Landroid/media/AudioAttributes;ILjava/util/List;)I",
-                                android_media_AudioSystem_getPreferredMixerAttributes),
-         MAKE_JNI_NATIVE_METHOD("clearPreferredMixerAttributes",
-                                "(Landroid/media/AudioAttributes;II)I",
-                                android_media_AudioSystem_clearPreferredMixerAttributes),
-         MAKE_AUDIO_SYSTEM_METHOD(supportsBluetoothVariableLatency),
-         MAKE_AUDIO_SYSTEM_METHOD(setBluetoothVariableLatencyEnabled),
-         MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled),
-         MAKE_JNI_NATIVE_METHOD("listenForSystemPropertyChange",
-                                "(Ljava/lang/String;Ljava/lang/Runnable;)J",
-                                android_media_AudioSystem_listenForSystemPropertyChange),
-         MAKE_JNI_NATIVE_METHOD("triggerSystemPropertyUpdate",
-                                "(J)V",
-                                android_media_AudioSystem_triggerSystemPropertyUpdate),
-
-        };
+static const JNINativeMethod gMethods[] = {
+        MAKE_AUDIO_SYSTEM_METHOD(setParameters),
+        MAKE_AUDIO_SYSTEM_METHOD(getParameters),
+        MAKE_AUDIO_SYSTEM_METHOD(muteMicrophone),
+        MAKE_AUDIO_SYSTEM_METHOD(isMicrophoneMuted),
+        MAKE_AUDIO_SYSTEM_METHOD(isStreamActive),
+        MAKE_AUDIO_SYSTEM_METHOD(isStreamActiveRemotely),
+        MAKE_AUDIO_SYSTEM_METHOD(isSourceActive),
+        MAKE_AUDIO_SYSTEM_METHOD(newAudioSessionId),
+        MAKE_AUDIO_SYSTEM_METHOD(newAudioPlayerId),
+        MAKE_AUDIO_SYSTEM_METHOD(newAudioRecorderId),
+        MAKE_JNI_NATIVE_METHOD("setDeviceConnectionState", "(ILandroid/os/Parcel;I)I",
+                               android_media_AudioSystem_setDeviceConnectionState),
+        MAKE_AUDIO_SYSTEM_METHOD(getDeviceConnectionState),
+        MAKE_AUDIO_SYSTEM_METHOD(handleDeviceConfigChange),
+        MAKE_AUDIO_SYSTEM_METHOD(setPhoneState),
+        MAKE_AUDIO_SYSTEM_METHOD(setForceUse),
+        MAKE_AUDIO_SYSTEM_METHOD(getForceUse),
+        MAKE_AUDIO_SYSTEM_METHOD(setDeviceAbsoluteVolumeEnabled),
+        MAKE_AUDIO_SYSTEM_METHOD(initStreamVolume),
+        MAKE_AUDIO_SYSTEM_METHOD(setStreamVolumeIndex),
+        MAKE_AUDIO_SYSTEM_METHOD(getStreamVolumeIndex),
+        MAKE_JNI_NATIVE_METHOD("setVolumeIndexForAttributes",
+                               "(Landroid/media/AudioAttributes;IZI)I",
+                               android_media_AudioSystem_setVolumeIndexForAttributes),
+        MAKE_JNI_NATIVE_METHOD("getVolumeIndexForAttributes", "(Landroid/media/AudioAttributes;I)I",
+                               android_media_AudioSystem_getVolumeIndexForAttributes),
+        MAKE_JNI_NATIVE_METHOD("getMinVolumeIndexForAttributes",
+                               "(Landroid/media/AudioAttributes;)I",
+                               android_media_AudioSystem_getMinVolumeIndexForAttributes),
+        MAKE_JNI_NATIVE_METHOD("getMaxVolumeIndexForAttributes",
+                               "(Landroid/media/AudioAttributes;)I",
+                               android_media_AudioSystem_getMaxVolumeIndexForAttributes),
+        MAKE_AUDIO_SYSTEM_METHOD(setMasterVolume),
+        MAKE_AUDIO_SYSTEM_METHOD(getMasterVolume),
+        MAKE_AUDIO_SYSTEM_METHOD(setMasterMute),
+        MAKE_AUDIO_SYSTEM_METHOD(getMasterMute),
+        MAKE_AUDIO_SYSTEM_METHOD(setMasterMono),
+        MAKE_AUDIO_SYSTEM_METHOD(getMasterMono),
+        MAKE_AUDIO_SYSTEM_METHOD(setMasterBalance),
+        MAKE_AUDIO_SYSTEM_METHOD(getMasterBalance),
+        MAKE_AUDIO_SYSTEM_METHOD(getPrimaryOutputSamplingRate),
+        MAKE_AUDIO_SYSTEM_METHOD(getPrimaryOutputFrameCount),
+        MAKE_AUDIO_SYSTEM_METHOD(getOutputLatency),
+        MAKE_AUDIO_SYSTEM_METHOD(setLowRamDevice),
+        MAKE_AUDIO_SYSTEM_METHOD(checkAudioFlinger),
+        MAKE_JNI_NATIVE_METHOD("setAudioFlingerBinder", "(Landroid/os/IBinder;)V",
+                               android_media_AudioSystem_setAudioFlingerBinder),
+        MAKE_JNI_NATIVE_METHOD("listAudioPorts", "(Ljava/util/ArrayList;[I)I",
+                               android_media_AudioSystem_listAudioPorts),
+        MAKE_JNI_NATIVE_METHOD("getSupportedDeviceTypes", "(ILandroid/util/IntArray;)I",
+                               android_media_AudioSystem_getSupportedDeviceTypes),
+        MAKE_JNI_NATIVE_METHOD("createAudioPatch",
+                               "([Landroid/media/AudioPatch;[Landroid/media/"
+                               "AudioPortConfig;[Landroid/media/AudioPortConfig;)I",
+                               android_media_AudioSystem_createAudioPatch),
+        MAKE_JNI_NATIVE_METHOD("releaseAudioPatch", "(Landroid/media/AudioPatch;)I",
+                               android_media_AudioSystem_releaseAudioPatch),
+        MAKE_JNI_NATIVE_METHOD("listAudioPatches", "(Ljava/util/ArrayList;[I)I",
+                               android_media_AudioSystem_listAudioPatches),
+        MAKE_JNI_NATIVE_METHOD("setAudioPortConfig", "(Landroid/media/AudioPortConfig;)I",
+                               android_media_AudioSystem_setAudioPortConfig),
+        MAKE_JNI_NATIVE_METHOD("startAudioSource",
+                               "(Landroid/media/AudioPortConfig;Landroid/media/AudioAttributes;)I",
+                               android_media_AudioSystem_startAudioSource),
+        MAKE_AUDIO_SYSTEM_METHOD(stopAudioSource),
+        MAKE_AUDIO_SYSTEM_METHOD(getAudioHwSyncForSession),
+        MAKE_JNI_NATIVE_METHOD("registerPolicyMixes", "(Ljava/util/ArrayList;Z)I",
+                               android_media_AudioSystem_registerPolicyMixes),
+        MAKE_JNI_NATIVE_METHOD("getRegisteredPolicyMixes", "(Ljava/util/List;)I",
+                               android_media_AudioSystem_getRegisteredPolicyMixes),
+        MAKE_JNI_NATIVE_METHOD("updatePolicyMixes",
+                               "([Landroid/media/audiopolicy/AudioMix;[Landroid/media/audiopolicy/"
+                               "AudioMixingRule;)I",
+                               android_media_AudioSystem_updatePolicyMixes),
+        MAKE_JNI_NATIVE_METHOD("setUidDeviceAffinities", "(I[I[Ljava/lang/String;)I",
+                               android_media_AudioSystem_setUidDeviceAffinities),
+        MAKE_AUDIO_SYSTEM_METHOD(removeUidDeviceAffinities),
+        MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_dynamic_policy_callback",
+                                       android_media_AudioSystem_registerDynPolicyCallback),
+        MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_recording_callback",
+                                       android_media_AudioSystem_registerRecordingCallback),
+        MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_routing_callback",
+                                       android_media_AudioSystem_registerRoutingCallback),
+        MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_register_vol_range_init_req_callback",
+                                       android_media_AudioSystem_registerVolRangeInitReqCallback),
+        MAKE_AUDIO_SYSTEM_METHOD(systemReady),
+        MAKE_AUDIO_SYSTEM_METHOD(getStreamVolumeDB),
+        MAKE_JNI_NATIVE_METHOD_AUTOSIG("native_get_offload_support",
+                                       android_media_AudioSystem_getOffloadSupport),
+        MAKE_JNI_NATIVE_METHOD("getMicrophones", "(Ljava/util/ArrayList;)I",
+                               android_media_AudioSystem_getMicrophones),
+        MAKE_JNI_NATIVE_METHOD("getSurroundFormats", "(Ljava/util/Map;)I",
+                               android_media_AudioSystem_getSurroundFormats),
+        MAKE_JNI_NATIVE_METHOD("getReportedSurroundFormats", "(Ljava/util/ArrayList;)I",
+                               android_media_AudioSystem_getReportedSurroundFormats),
+        MAKE_AUDIO_SYSTEM_METHOD(setSurroundFormatEnabled),
+        MAKE_AUDIO_SYSTEM_METHOD(setAssistantServicesUids),
+        MAKE_AUDIO_SYSTEM_METHOD(setActiveAssistantServicesUids),
+        MAKE_AUDIO_SYSTEM_METHOD(setA11yServicesUids),
+        MAKE_AUDIO_SYSTEM_METHOD(isHapticPlaybackSupported),
+        MAKE_AUDIO_SYSTEM_METHOD(isUltrasoundSupported),
+        MAKE_JNI_NATIVE_METHOD(
+                "getHwOffloadFormatsSupportedForBluetoothMedia", "(ILjava/util/ArrayList;)I",
+                android_media_AudioSystem_getHwOffloadFormatsSupportedForBluetoothMedia),
+        MAKE_AUDIO_SYSTEM_METHOD(setSupportedSystemUsages),
+        MAKE_AUDIO_SYSTEM_METHOD(setAllowedCapturePolicy),
+        MAKE_AUDIO_SYSTEM_METHOD(setRttEnabled),
+        MAKE_AUDIO_SYSTEM_METHOD(setAudioHalPids),
+        MAKE_AUDIO_SYSTEM_METHOD(isCallScreeningModeSupported),
+        MAKE_JNI_NATIVE_METHOD("setDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I",
+                               android_media_AudioSystem_setDevicesRoleForStrategy),
+        MAKE_JNI_NATIVE_METHOD("removeDevicesRoleForStrategy", "(II[I[Ljava/lang/String;)I",
+                               android_media_AudioSystem_removeDevicesRoleForStrategy),
+        MAKE_AUDIO_SYSTEM_METHOD(clearDevicesRoleForStrategy),
+        MAKE_JNI_NATIVE_METHOD("getDevicesForRoleAndStrategy", "(IILjava/util/List;)I",
+                               android_media_AudioSystem_getDevicesForRoleAndStrategy),
+        MAKE_JNI_NATIVE_METHOD("setDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
+                               android_media_AudioSystem_setDevicesRoleForCapturePreset),
+        MAKE_JNI_NATIVE_METHOD("addDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
+                               android_media_AudioSystem_addDevicesRoleForCapturePreset),
+        MAKE_JNI_NATIVE_METHOD("removeDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
+                               android_media_AudioSystem_removeDevicesRoleForCapturePreset),
+        MAKE_AUDIO_SYSTEM_METHOD(clearDevicesRoleForCapturePreset),
+        MAKE_JNI_NATIVE_METHOD("getDevicesForRoleAndCapturePreset", "(IILjava/util/List;)I",
+                               android_media_AudioSystem_getDevicesForRoleAndCapturePreset),
+        MAKE_JNI_NATIVE_METHOD("getDevicesForAttributes",
+                               "(Landroid/media/AudioAttributes;[Landroid/media/"
+                               "AudioDeviceAttributes;Z)I",
+                               android_media_AudioSystem_getDevicesForAttributes),
+        MAKE_JNI_NATIVE_METHOD("setUserIdDeviceAffinities", "(I[I[Ljava/lang/String;)I",
+                               android_media_AudioSystem_setUserIdDeviceAffinities),
+        MAKE_AUDIO_SYSTEM_METHOD(removeUserIdDeviceAffinities),
+        MAKE_AUDIO_SYSTEM_METHOD(setCurrentImeUid),
+        MAKE_JNI_NATIVE_METHOD("setVibratorInfos", "(Ljava/util/List;)I",
+                               android_media_AudioSystem_setVibratorInfos),
+        MAKE_JNI_NATIVE_METHOD("nativeGetSpatializer",
+                               "(Landroid/media/INativeSpatializerCallback;)Landroid/os/IBinder;",
+                               android_media_AudioSystem_getSpatializer),
+        MAKE_JNI_NATIVE_METHOD("canBeSpatialized",
+                               "(Landroid/media/AudioAttributes;Landroid/media/AudioFormat;"
+                               "[Landroid/media/AudioDeviceAttributes;)Z",
+                               android_media_AudioSystem_canBeSpatialized),
+        MAKE_JNI_NATIVE_METHOD("nativeGetSoundDose",
+                               "(Landroid/media/ISoundDoseCallback;)Landroid/os/IBinder;",
+                               android_media_AudioSystem_nativeGetSoundDose),
+        MAKE_JNI_NATIVE_METHOD("getDirectPlaybackSupport",
+                               "(Landroid/media/AudioFormat;Landroid/media/AudioAttributes;)I",
+                               android_media_AudioSystem_getDirectPlaybackSupport),
+        MAKE_JNI_NATIVE_METHOD("getDirectProfilesForAttributes",
+                               "(Landroid/media/AudioAttributes;Ljava/util/ArrayList;)I",
+                               android_media_AudioSystem_getDirectProfilesForAttributes),
+        MAKE_JNI_NATIVE_METHOD("getSupportedMixerAttributes", "(ILjava/util/List;)I",
+                               android_media_AudioSystem_getSupportedMixerAttributes),
+        MAKE_JNI_NATIVE_METHOD("setPreferredMixerAttributes",
+                               "(Landroid/media/AudioAttributes;IILandroid/media/"
+                               "AudioMixerAttributes;)I",
+                               android_media_AudioSystem_setPreferredMixerAttributes),
+        MAKE_JNI_NATIVE_METHOD("getPreferredMixerAttributes",
+                               "(Landroid/media/AudioAttributes;ILjava/util/List;)I",
+                               android_media_AudioSystem_getPreferredMixerAttributes),
+        MAKE_JNI_NATIVE_METHOD("clearPreferredMixerAttributes",
+                               "(Landroid/media/AudioAttributes;II)I",
+                               android_media_AudioSystem_clearPreferredMixerAttributes),
+        MAKE_AUDIO_SYSTEM_METHOD(supportsBluetoothVariableLatency),
+        MAKE_AUDIO_SYSTEM_METHOD(setBluetoothVariableLatencyEnabled),
+        MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled),
+        MAKE_JNI_NATIVE_METHOD("listenForSystemPropertyChange",
+                               "(Ljava/lang/String;Ljava/lang/Runnable;)J",
+                               android_media_AudioSystem_listenForSystemPropertyChange),
+        MAKE_JNI_NATIVE_METHOD("triggerSystemPropertyUpdate", "(J)V",
+                               android_media_AudioSystem_triggerSystemPropertyUpdate),
+};
 
 static const JNINativeMethod gEventHandlerMethods[] =
         {MAKE_JNI_NATIVE_METHOD("native_setup", "(Ljava/lang/Object;)V",
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index a939d92..755704a 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -115,6 +115,7 @@
     jfieldID supportedDisplayModes;
     jfieldID activeDisplayModeId;
     jfieldID renderFrameRate;
+    jfieldID hasArrSupport;
     jfieldID supportedColorModes;
     jfieldID activeColorMode;
     jfieldID hdrCapabilities;
@@ -1453,7 +1454,7 @@
     env->SetIntField(object, gDynamicDisplayInfoClassInfo.activeDisplayModeId,
                      info.activeDisplayModeId);
     env->SetFloatField(object, gDynamicDisplayInfoClassInfo.renderFrameRate, info.renderFrameRate);
-
+    env->SetBooleanField(object, gDynamicDisplayInfoClassInfo.hasArrSupport, info.hasArrSupport);
     jintArray colorModesArray = env->NewIntArray(info.supportedColorModes.size());
     if (colorModesArray == NULL) {
         jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
@@ -2641,6 +2642,8 @@
             GetFieldIDOrDie(env, dynamicInfoClazz, "activeDisplayModeId", "I");
     gDynamicDisplayInfoClassInfo.renderFrameRate =
             GetFieldIDOrDie(env, dynamicInfoClazz, "renderFrameRate", "F");
+    gDynamicDisplayInfoClassInfo.hasArrSupport =
+            GetFieldIDOrDie(env, dynamicInfoClazz, "hasArrSupport", "Z");
     gDynamicDisplayInfoClassInfo.supportedColorModes =
             GetFieldIDOrDie(env, dynamicInfoClazz, "supportedColorModes", "[I");
     gDynamicDisplayInfoClassInfo.activeColorMode =
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 06621c9..bd403c7 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -262,7 +262,8 @@
 }
 
 // returns result from java.lang.System.getProperty
-static string getJavaProperty(JNIEnv* env, const char* property_name) {
+static string getJavaProperty(JNIEnv* env, const char* property_name,
+                              const char* defaultValue = "") {
     jclass system = FindClassOrDie(env, "java/lang/System");
     jmethodID getPropertyMethod =
             GetStaticMethodIDOrDie(env, system, "getProperty",
@@ -270,7 +271,7 @@
 
     auto jString = (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
                                                         env->NewStringUTF(property_name),
-                                                        env->NewStringUTF(""));
+                                                        env->NewStringUTF(defaultValue));
     ScopedUtfChars chars(env, jString);
     return string(chars.c_str());
 }
@@ -430,7 +431,6 @@
 
 } // namespace android
 
-#ifndef _WIN32
 using namespace android;
 
 JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
@@ -439,12 +439,14 @@
         return JNI_ERR;
     }
 
-    Vector<String8> args;
-    HostRuntime runtime;
+    string useBaseHostRuntime = getJavaProperty(env, "use_base_native_hostruntime", "true");
+    if (useBaseHostRuntime == "true") {
+        Vector<String8> args;
+        HostRuntime runtime;
 
-    runtime.onVmCreated(env);
-    runtime.start("HostRuntime", args, false);
+        runtime.onVmCreated(env);
+        runtime.start("HostRuntime", args, false);
+    }
 
     return JNI_VERSION_1_6;
 }
-#endif
diff --git a/core/res/res/color-watch-v36/btn_material_filled_background_color.xml b/core/res/res/color-watch-v36/btn_material_filled_background_color.xml
deleted file mode 100644
index 8b2afa8..0000000
--- a/core/res/res/color-watch-v36/btn_material_filled_background_color.xml
+++ /dev/null
@@ -1,23 +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.
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false"
-          android:alpha="?attr/disabledAlpha"
-          android:color="?attr/materialColorOnSurface" />
-    <item android:state_enabled="true"
-          android:color="?attr/materialColorPrimary" />
-</selector>
\ No newline at end of file
diff --git a/core/res/res/color-watch-v36/btn_material_filled_text_color.xml b/core/res/res/color-watch-v36/btn_material_filled_text_color.xml
deleted file mode 100644
index cefc912..0000000
--- a/core/res/res/color-watch-v36/btn_material_filled_text_color.xml
+++ /dev/null
@@ -1,23 +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.
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false"
-          android:alpha="?attr/primaryContentAlpha"
-          android:color="?attr/materialColorOnSurface" />
-    <item android:state_enabled="true"
-          android:color="?attr/materialColorOnPrimary" />
-</selector>
\ No newline at end of file
diff --git a/core/res/res/color-watch-v36/btn_material_filled_tonal_background_color.xml b/core/res/res/color-watch-v36/btn_material_filled_tonal_background_color.xml
deleted file mode 100644
index eaf9e7d..0000000
--- a/core/res/res/color-watch-v36/btn_material_filled_tonal_background_color.xml
+++ /dev/null
@@ -1,23 +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.
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false"
-          android:alpha="?attr/disabledAlpha"
-          android:color="?attr/materialColorOnSurface" />
-    <item android:state_enabled="true"
-          android:color="?attr/materialColorSurfaceContainer" />
-</selector>
\ No newline at end of file
diff --git a/core/res/res/color-watch-v36/btn_material_filled_tonal_text_color.xml b/core/res/res/color-watch-v36/btn_material_filled_tonal_text_color.xml
deleted file mode 100644
index 94e50fb..0000000
--- a/core/res/res/color-watch-v36/btn_material_filled_tonal_text_color.xml
+++ /dev/null
@@ -1,23 +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.
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false"
-          android:alpha="?attr/primaryContentAlpha"
-          android:color="?attr/materialColorOnSurface" />
-    <item android:state_enabled="true"
-          android:color="?attr/materialColorOnSurface" />
-</selector>
\ No newline at end of file
diff --git a/core/res/res/drawable-watch-v36/btn_background_material_filled.xml b/core/res/res/drawable-watch-v36/btn_background_material_filled.xml
deleted file mode 100644
index 0029de1..0000000
--- a/core/res/res/drawable-watch-v36/btn_background_material_filled.xml
+++ /dev/null
@@ -1,28 +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.
-  -->
-
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="?attr/colorControlHighlight">
-    <item>
-        <shape android:shape="rectangle">
-            <solid android:color="@color/btn_material_filled_background_color"/>
-            <corners android:radius="?android:attr/buttonCornerRadius"/>
-            <size
-                android:width="@dimen/btn_material_width"
-                android:height="@dimen/btn_material_height" />
-        </shape>
-    </item>
-</ripple>
\ No newline at end of file
diff --git a/core/res/res/drawable-watch-v36/btn_background_material_filled_tonal.xml b/core/res/res/drawable-watch-v36/btn_background_material_filled_tonal.xml
deleted file mode 100644
index 105f077..0000000
--- a/core/res/res/drawable-watch-v36/btn_background_material_filled_tonal.xml
+++ /dev/null
@@ -1,28 +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.
-  -->
-
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="?attr/colorControlHighlight">
-    <item>
-        <shape android:shape="rectangle">
-            <solid android:color="@color/btn_material_filled_tonal_background_color"/>
-            <corners android:radius="?android:attr/buttonCornerRadius"/>
-            <size
-                android:width="@dimen/btn_material_width"
-                android:height="@dimen/btn_material_height" />
-        </shape>
-    </item>
-</ripple>
\ No newline at end of file
diff --git a/core/res/res/values-watch-v36/colors.xml b/core/res/res/values-watch-v36/colors.xml
deleted file mode 100644
index 4bc2a66..0000000
--- a/core/res/res/values-watch-v36/colors.xml
+++ /dev/null
@@ -1,18 +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.
-  -->
-<!-- TODO(b/372524566): update color token's value to match material3 design. -->
-<resources>
-</resources>
\ No newline at end of file
diff --git a/core/res/res/values-watch-v36/config.xml b/core/res/res/values-watch-v36/config.xml
deleted file mode 100644
index c8f347af..0000000
--- a/core/res/res/values-watch-v36/config.xml
+++ /dev/null
@@ -1,20 +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.
-  -->
-
-<resources>
-    <!-- Overrides system value -->
-    <dimen name="config_buttonCornerRadius">26dp</dimen>
-</resources>
diff --git a/core/res/res/values-watch-v36/dimens_material.xml b/core/res/res/values-watch-v36/dimens_material.xml
deleted file mode 100644
index ad3c1a3..0000000
--- a/core/res/res/values-watch-v36/dimens_material.xml
+++ /dev/null
@@ -1,28 +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.
-  -->
-<resources>
-    <!-- values for material3 button -->
-    <dimen name="btn_material_width">172dp</dimen>
-    <dimen name="btn_material_height">52dp</dimen>
-    <dimen name="btn_horizontal_edge_padding">14dp</dimen>
-    <dimen name="btn_drawable_padding">6dp</dimen>
-    <dimen name="btn_lineHeight">18sp</dimen>
-    <dimen name="btn_textSize">15sp</dimen>
-
-    <!-- Opacity factor for disabled material3 widget -->
-    <dimen name="disabled_alpha_device_default">0.12</dimen>
-    <dimen name="primary_content_alpha_device_default">0.38</dimen>
-</resources>
diff --git a/core/res/res/values-watch-v36/styles_material.xml b/core/res/res/values-watch-v36/styles_material.xml
deleted file mode 100644
index 32a22bb..0000000
--- a/core/res/res/values-watch-v36/styles_material.xml
+++ /dev/null
@@ -1,58 +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.
-  -->
-
-<resources>
-    <!--  Button Styles  -->
-    <!-- Material Button - Filled  -->
-    <style name="Widget.DeviceDefault.Button.Filled" parent="Widget.DeviceDefault.Button">
-        <item name="android:background">@drawable/btn_background_material_filled</item>
-        <item name="textAppearance">@style/TextAppearance.Widget.Button.Material.Filled</item>
-    </style>
-
-    <!-- Material Button - Filled Tonal(Override system default button styles) -->
-    <style name="Widget.DeviceDefault.Button">
-        <item name="background">@drawable/btn_background_material_filled_tonal</item>
-        <item name="textAppearance">@style/TextAppearance.Widget.Button.Material</item>
-        <item name="minHeight">@dimen/btn_material_height</item>
-        <item name="maxWidth">@dimen/btn_material_width</item>
-        <item name="android:paddingStart">@dimen/btn_horizontal_edge_padding</item>
-        <item name="android:paddingEnd">@dimen/btn_horizontal_edge_padding</item>
-        <item name="android:drawablePadding">@dimen/btn_drawable_padding</item>
-        <item name="android:maxLines">2</item>
-        <item name="android:ellipsize">end</item>
-        <item name="android:breakStrategy">simple</item>
-        <item name="stateListAnimator">@anim/button_state_list_anim_material</item>
-        <item name="focusable">true</item>
-        <item name="clickable">true</item>
-        <item name="gravity">center_vertical</item>
-    </style>
-
-    <!--  Text Styles  -->
-    <!-- TextAppearance for Material Button - Filled  -->
-    <style name="TextAppearance.Widget.Button.Material.Filled" parent="TextAppearance.Widget.Button.Material">
-        <item name="textColor">@color/btn_material_filled_text_color</item>
-    </style>
-
-    <!-- TextAppearance for Material Button - Filled Tonal  -->
-    <style name="TextAppearance.Widget.Button.Material" parent="TextAppearance.DeviceDefault">
-        <item name="android:fontFamily">font-family-flex-device-default</item>
-        <item name="android:fontVariationSettings">"'wdth' 90, 'wght' 500, 'ROND' 100, 'opsz' 15, 'GRAD' 0"</item>
-        <item name="textSize">@dimen/btn_textSize</item>
-        <item name="textColor">@color/btn_material_filled_tonal_text_color</item>
-        <item name="lineHeight">@dimen/btn_lineHeight</item>
-    </style>
-</resources>
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 48e2620..23a0985 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -2397,10 +2397,25 @@
     public void progressStyle_getProgressMax_returnsSumOfSegmentLength() {
         final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
         progressStyle
+                .setProgressSegments(List.of(new Notification.ProgressStyle.Segment(15),
+                        new Notification.ProgressStyle.Segment(25)))
                 .addProgressSegment(new Notification.ProgressStyle.Segment(10))
                 .addProgressSegment(new Notification.ProgressStyle.Segment(20));
 
-        assertThat(progressStyle.getProgressMax()).isEqualTo(30);
+        assertThat(progressStyle.getProgressMax()).isEqualTo(70);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+    public void progressStyle_getProgressMax_onSetProgressSegments_resets() {
+        final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+        progressStyle
+                .addProgressSegment(new Notification.ProgressStyle.Segment(10))
+                .addProgressSegment(new Notification.ProgressStyle.Segment(20))
+                .setProgressSegments(List.of(new Notification.ProgressStyle.Segment(15),
+                        new Notification.ProgressStyle.Segment(25)));
+
+        assertThat(progressStyle.getProgressMax()).isEqualTo(40);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 0621d82..2880ecf 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -16,6 +16,8 @@
 
 package android.widget;
 
+import static android.appwidget.flags.Flags.remoteAdapterConversion;
+
 import static com.android.internal.R.id.pending_intent_tag;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -282,7 +284,10 @@
         widget.addView(view);
 
         ListView listView = (ListView) view.findViewById(R.id.list);
-        listView.onRemoteAdapterConnected();
+
+        if (!remoteAdapterConversion()) {
+            listView.onRemoteAdapterConnected();
+        }
         assertNotNull(listView.getAdapter());
 
         top.reapply(mContext, view);
@@ -414,6 +419,58 @@
         assertNotNull(view.findViewById(R.id.light_background_text));
     }
 
+    @Test
+    public void remoteCollectionItemsAdapter_lightBackgroundLayoutFlagSet() {
+        AppWidgetHostView widget = new AppWidgetHostView(mContext);
+        RemoteViews container = new RemoteViews(mPackage, R.layout.remote_view_host);
+        RemoteViews listRemoteViews = new RemoteViews(mPackage, R.layout.remote_views_list);
+        RemoteViews item = new RemoteViews(mPackage, R.layout.remote_views_text);
+        item.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text);
+        listRemoteViews.setRemoteAdapter(
+                R.id.list,
+                new RemoteViews.RemoteCollectionItems.Builder().addItem(0, item).build());
+        container.addView(R.id.container, listRemoteViews);
+
+        widget.setOnLightBackground(true);
+        widget.updateAppWidget(container);
+
+        // Populate the list view
+        ListView listView = (ListView) widget.findViewById(R.id.list);
+        int measureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
+        listView.measure(measureSpec, measureSpec);
+        listView.layout(0, 0, 100, 100);
+
+        // Picks the light background layout id for the item
+        assertNotNull(listView.getChildAt(0).findViewById(R.id.light_background_text));
+        assertNull(listView.getChildAt(0).findViewById(R.id.text));
+    }
+
+    @Test
+    public void remoteCollectionItemsAdapter_lightBackgroundLayoutFlagNotSet() {
+        AppWidgetHostView widget = new AppWidgetHostView(mContext);
+        RemoteViews container = new RemoteViews(mPackage, R.layout.remote_view_host);
+        RemoteViews listRemoteViews = new RemoteViews(mPackage, R.layout.remote_views_list);
+        RemoteViews item = new RemoteViews(mPackage, R.layout.remote_views_text);
+        item.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text);
+        listRemoteViews.setRemoteAdapter(
+                R.id.list,
+                new RemoteViews.RemoteCollectionItems.Builder().addItem(0, item).build());
+        container.addView(R.id.container, listRemoteViews);
+
+        widget.setOnLightBackground(false);
+        widget.updateAppWidget(container);
+
+        // Populate the list view
+        ListView listView = (ListView) widget.findViewById(R.id.list);
+        int measureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
+        listView.measure(measureSpec, measureSpec);
+        listView.layout(0, 0, 100, 100);
+
+        // Does not pick the light background layout id for the item
+        assertNotNull(listView.getChildAt(0).findViewById(R.id.text));
+        assertNull(listView.getChildAt(0).findViewById(R.id.light_background_text));
+    }
+
     private RemoteViews createViewChained(int depth, String... texts) {
         RemoteViews result = new RemoteViews(mPackage, R.layout.remote_view_host);
 
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index b001cc2..46bd73e 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -21,6 +21,7 @@
 import static android.window.OnBackInvokedDispatcher.PRIORITY_SYSTEM_NAVIGATION_OBSERVER;
 
 import static com.android.window.flags.Flags.FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER;
+import static com.android.window.flags.Flags.FLAG_PREDICTIVE_BACK_TIMESTAMP_API;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -486,6 +487,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(FLAG_PREDICTIVE_BACK_TIMESTAMP_API)
     public void onBackInvoked_notCalledAfterCallbackUnregistration()
             throws RemoteException, InterruptedException {
         // Setup a callback that unregisters itself after the gesture is finished but before the
@@ -684,6 +686,7 @@
         return new BackMotionEvent(
                 /* touchX = */ 0,
                 /* touchY = */ 0,
+                /* frameTime = */ 0,
                 /* progress = */ progress,
                 /* triggerBack = */ false,
                 /* swipeEdge = */ BackEvent.EDGE_LEFT,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
index e3a1d8a..6ad2f08 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
@@ -148,8 +148,12 @@
             final TaskFragmentInfo info = mTaskFragmentInfos.valueAt(i);
             mPresenter.deleteTaskFragment(wct, info.getFragmentToken());
         }
-        mPresenter.setSavedState(new Bundle());
 
+        removeSavedState();
+    }
+
+    private void removeSavedState() {
+        mPresenter.setSavedState(new Bundle());
         mParcelableTaskContainerDataList.clear();
         mTaskFragmentInfos.clear();
         mTaskFragmentParentInfos.clear();
@@ -169,6 +173,19 @@
             return false;
         }
 
+        if (mTaskFragmentParentInfos.size() == 0) {
+            // No Task left in the WM hierarchy, remove the states and no need to restore.
+            if (DEBUG) Log.d(TAG, "Remove save states due to no task to restore.");
+            removeSavedState();
+            return false;
+        }
+
+        final ArrayList<Integer> taskIdsInSystem = new ArrayList<>();
+        for (int i = mTaskFragmentParentInfos.size() - 1; i >= 0; --i) {
+            final TaskFragmentParentInfo parentInfo = mTaskFragmentParentInfos.valueAt(i);
+            taskIdsInSystem.add(parentInfo.getTaskId());
+        }
+
         if (DEBUG) Log.d(TAG, "Rebuilding TaskContainers.");
         final ArrayMap<String, EmbeddingRule> embeddingRuleMap = new ArrayMap<>();
         for (EmbeddingRule rule : rules) {
@@ -190,6 +207,14 @@
             }
 
             mParcelableTaskContainerDataList.remove(parcelableTaskContainerData);
+            if (!taskIdsInSystem.contains(parcelableTaskContainerData.mTaskId)) {
+                if (DEBUG) {
+                    Log.d(TAG, "Rebuilding TaskContainer abort! Not existed. Task#"
+                            + parcelableTaskContainerData.mTaskId);
+                }
+                continue;
+            }
+
             final TaskContainer taskContainer = new TaskContainer(parcelableTaskContainerData,
                     mController, mTaskFragmentInfos);
             if (DEBUG) Log.d(TAG, "Created TaskContainer " + taskContainer);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 8345b40..3368e2e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -239,6 +239,11 @@
         mActivityStartMonitor = new ActivityStartMonitor();
         instrumentation.addMonitor(mActivityStartMonitor);
         foldingFeatureProducer.addDataChangedCallback(new FoldingFeatureListener());
+
+        synchronized (mLock) {
+            // Abort the restoration if any and the application already has running activities.
+            abortRebuildingTaskContainersIfNeeded(null /* launchingActivity */);
+        }
     }
 
     private class FoldingFeatureListener
@@ -285,6 +290,10 @@
                 return;
             }
 
+            if (abortRebuildingTaskContainersIfNeeded(null /* launchingActivity */)) {
+                return;
+            }
+
             try {
                 final TransactionRecord transactionRecord =
                         mTransactionManager.startNewTransaction();
@@ -2883,6 +2892,53 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private boolean abortRebuildingTaskContainersIfNeeded(@Nullable Activity launchingActivity) {
+        if (mPresenter == null || !mPresenter.isWaitingToRebuildTaskContainers()) {
+            return false;
+        }
+
+        final ActivityThread activityThread = ActivityThread.currentActivityThread();
+        if (activityThread == null) {
+            return false;
+        }
+
+        final Activity lastCreatedActivity = activityThread.getLastCreatedActivity();
+        final Activity activity =
+                launchingActivity != null ? launchingActivity : lastCreatedActivity;
+        if (activity == null) {
+            return false;
+        }
+
+        Log.w(TAG, "Rebuilding aborted, clean up.");
+
+        // Retrieve the Task intent.
+        final int taskId = getTaskId(activity);
+
+        // Clean up and abort the restoration
+        // TODO(b/369488857): also to remove the non-organized activities in the Task?
+        final TransactionRecord transactionRecord =
+                mTransactionManager.startNewTransaction();
+        final WindowContainerTransaction wct = transactionRecord.getTransaction();
+        mPresenter.abortTaskContainerRebuilding(wct);
+        transactionRecord.apply(false /* shouldApplyIndependently */);
+
+        // Start the Task root activity if the task is now empty.
+        ActivityManager.RecentTaskInfo taskInfo = null;
+        final ActivityManager am = activity.getSystemService(ActivityManager.class);
+        final List<ActivityManager.AppTask> appTasks = am.getAppTasks();
+        for (ActivityManager.AppTask appTask : appTasks) {
+            if (appTask.getTaskInfo().taskId == taskId) {
+                taskInfo = appTask.getTaskInfo();
+                break;
+            }
+        }
+        if (taskInfo != null && !taskInfo.isRunning) {
+            activity.startActivity(taskInfo.baseIntent.cloneFilter());
+        }
+        return true;
+    }
+
     private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
 
         @Override
@@ -2894,33 +2950,7 @@
                 return;
             }
             synchronized (mLock) {
-                if (mPresenter.isWaitingToRebuildTaskContainers()) {
-                    Log.w(TAG, "Rebuilding aborted, clean up and restart");
-
-                    // Retrieve the Task intent.
-                    final int taskId = getTaskId(activity);
-                    Intent taskIntent = null;
-                    final ActivityManager am = activity.getSystemService(ActivityManager.class);
-                    final List<ActivityManager.AppTask> appTasks = am.getAppTasks();
-                    for (ActivityManager.AppTask appTask : appTasks) {
-                        if (appTask.getTaskInfo().taskId == taskId) {
-                            taskIntent = appTask.getTaskInfo().baseIntent.cloneFilter();
-                            break;
-                        }
-                    }
-
-                    // Clean up and abort the restoration
-                    // TODO(b/369488857): also to remove the non-organized activities in the Task?
-                    final TransactionRecord transactionRecord =
-                            mTransactionManager.startNewTransaction();
-                    final WindowContainerTransaction wct = transactionRecord.getTransaction();
-                    mPresenter.abortTaskContainerRebuilding(wct);
-                    transactionRecord.apply(false /* shouldApplyIndependently */);
-
-                    // Start the Task root activity.
-                    if (taskIntent != null) {
-                        activity.startActivity(taskIntent);
-                    }
+                if (abortRebuildingTaskContainersIfNeeded(activity)) {
                     return;
                 }
 
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
index 61cd1c3..5a2a723 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
@@ -61,11 +61,30 @@
     @IntDef(prefix = {"SPLIT_POSITION_"}, value = {
             SPLIT_POSITION_UNDEFINED,
             SPLIT_POSITION_TOP_OR_LEFT,
-            SPLIT_POSITION_BOTTOM_OR_RIGHT
+            SPLIT_POSITION_BOTTOM_OR_RIGHT,
     })
     public @interface SplitPosition {
     }
 
+    // These SPLIT_INDEX constants will be used in the same way as the above SPLIT_POSITION ints,
+    // but scalable to n apps. Eventually, SPLIT_POSITION can be deprecated and only the below
+    // will be used.
+    public static final int SPLIT_INDEX_UNDEFINED = -1;
+    public static final int SPLIT_INDEX_0 = 0;
+    public static final int SPLIT_INDEX_1 = 1;
+    public static final int SPLIT_INDEX_2 = 2;
+    public static final int SPLIT_INDEX_3 = 3;
+
+    @IntDef(prefix = {"SPLIT_INDEX_"}, value = {
+            SPLIT_INDEX_UNDEFINED,
+            SPLIT_INDEX_0,
+            SPLIT_INDEX_1,
+            SPLIT_INDEX_2,
+            SPLIT_INDEX_3
+    })
+    public @interface SplitIndex {
+    }
+
     /**
      * A snap target for two apps, where the split is 33-66. With FLAG_ENABLE_FLEXIBLE_SPLIT,
      * only used on tablets.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index b90e6e2..19b51f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -355,11 +355,8 @@
                 int keyAction,
                 @BackEvent.SwipeEdge int swipeEdge
         ) {
-            mShellExecutor.execute(() -> onMotionEvent(
-                    /* touchX = */ touchX,
-                    /* touchY = */ touchY,
-                    /* keyAction = */ keyAction,
-                    /* swipeEdge = */ swipeEdge));
+            mShellExecutor.execute(
+                    () -> onMotionEvent(touchX, touchY, keyAction, swipeEdge));
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index a8a8c2a..c81ffda 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1979,11 +1979,15 @@
 
         @Override
         public void addBubble(Bubble addedBubble) {
+            // Only log metrics event
+            mLogger.log(addedBubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_POSTED);
             // Nothing to do for adds, these are handled by launcher / in the bubble bar.
         }
 
         @Override
         public void updateBubble(Bubble updatedBubble) {
+            // Only log metrics event
+            mLogger.log(updatedBubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_UPDATED);
             // Nothing to do for updates, these are handled by launcher / in the bubble bar.
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
index 1abe119..6d757d2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.bubbles;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.util.FrameworkStatsLog;
@@ -33,7 +32,6 @@
     /**
      * Bubble UI event.
      */
-    @VisibleForTesting
     public enum Event implements UiEventLogger.UiEventEnum {
 
         // region bubble events
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index 9f100fa..0b2b3e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -19,9 +19,11 @@
 import static android.view.WindowManager.DOCKED_LEFT;
 import static android.view.WindowManager.DOCKED_RIGHT;
 
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_33_66;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_66_33;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_MINIMIZE;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_NONE;
@@ -33,6 +35,9 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.wm.shell.Flags;
+import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
+
 import java.util.ArrayList;
 
 /**
@@ -79,7 +84,9 @@
     private final int mTaskHeightInMinimizedMode;
     private final float mFixedRatio;
     /** Allows split ratios to calculated dynamically instead of using {@link #mFixedRatio}. */
-    private final boolean mAllowFlexibleSplitRatios;
+    private final boolean mCalculateRatiosBasedOnAvailableSpace;
+    /** Allows split ratios that go offscreen (a.k.a. "flexible split") */
+    private final boolean mAllowOffscreenRatios;
     private final boolean mIsHorizontalDivision;
 
     /** The first target which is still splitting the screen */
@@ -119,8 +126,11 @@
                 com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1);
         mMinimalSizeResizableTask = res.getDimensionPixelSize(
                 com.android.internal.R.dimen.default_minimal_size_resizable_task);
-        mAllowFlexibleSplitRatios = res.getBoolean(
+        mCalculateRatiosBasedOnAvailableSpace = res.getBoolean(
                 com.android.internal.R.bool.config_flexibleSplitRatios);
+        // If this is a small screen or a foldable, use offscreen ratios
+        mAllowOffscreenRatios =
+                Flags.enableFlexibleTwoAppSplit() && SplitScreenUtils.allowOffscreenRatios(res);
         mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize(
                 com.android.internal.R.dimen.task_height_of_minimized_mode) : 0;
         calculateTargets(isHorizontalDivision, dockSide);
@@ -233,6 +243,11 @@
         return mFirstSplitTarget.position < position && position < mLastSplitTarget.position;
     }
 
+    /** Returns if we are currently on a device/screen that supports split apps going offscreen. */
+    public boolean areOffscreenRatiosSupported() {
+        return mAllowOffscreenRatios;
+    }
+
     private SnapTarget snap(int position, boolean hardDismiss) {
         if (shouldApplyFreeSnapMode(position)) {
             return new SnapTarget(position, SNAP_TO_NONE);
@@ -283,10 +298,14 @@
 
     private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition,
             int bottomPosition, int dividerMax) {
-        maybeAddTarget(topPosition, topPosition - getStartInset(), SNAP_TO_2_33_66);
+        @PersistentSnapPosition int firstTarget =
+                mAllowOffscreenRatios ? SNAP_TO_2_10_90 : SNAP_TO_2_33_66;
+        @PersistentSnapPosition int lastTarget =
+                mAllowOffscreenRatios ? SNAP_TO_2_90_10 : SNAP_TO_2_66_33;
+        maybeAddTarget(topPosition, topPosition - getStartInset(), firstTarget);
         addMiddleTarget(isHorizontalDivision);
         maybeAddTarget(bottomPosition,
-                dividerMax - getEndInset() - (bottomPosition + mDividerSize), SNAP_TO_2_66_33);
+                dividerMax - getEndInset() - (bottomPosition + mDividerSize), lastTarget);
     }
 
     private void addFixedDivisionTargets(boolean isHorizontalDivision, int dividerMax) {
@@ -295,7 +314,11 @@
                 ? mDisplayHeight - mInsets.bottom
                 : mDisplayWidth - mInsets.right;
         int size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2;
-        if (mAllowFlexibleSplitRatios) {
+
+        if (mAllowOffscreenRatios) {
+            // TODO (b/349828130): This is a placeholder value, real measurements to come
+            size = (int) (0.3f * (end - start)) - mDividerSize / 2;
+        } else if (mCalculateRatiosBasedOnAvailableSpace) {
             size = Math.max(size, mMinimalSizeResizableTask);
         }
         int topPosition = start + size;
@@ -324,7 +347,7 @@
      * meets the minimal size requirement.
      */
     private void maybeAddTarget(int position, int smallerSize, @SnapPosition int snapPosition) {
-        if (smallerSize >= mMinimalSizeResizableTask) {
+        if (smallerSize >= mMinimalSizeResizableTask || mAllowOffscreenRatios) {
             mTargets.add(new SnapTarget(position, snapPosition));
         }
     }
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 83ffaf4..c9c3aa0 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
@@ -30,6 +30,8 @@
 import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED;
 import static com.android.wm.shell.shared.animation.Interpolators.LINEAR;
 import static com.android.wm.shell.shared.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -438,12 +440,31 @@
             dividerBounds.right = dividerBounds.left + mDividerWindowWidth;
             bounds1.right = position;
             bounds2.left = bounds1.right + mDividerSize;
+
+            // For flexible split, expand app offscreen as well
+            if (mDividerSnapAlgorithm.areOffscreenRatiosSupported()) {
+                if (position <= mDividerSnapAlgorithm.getMiddleTarget().position) {
+                    bounds1.left = bounds1.right - bounds2.width();
+                } else {
+                    bounds2.right = bounds2.left + bounds1.width();
+                }
+            }
+
         } else {
             position += mRootBounds.top;
             dividerBounds.top = position - mDividerInsets;
             dividerBounds.bottom = dividerBounds.top + mDividerWindowWidth;
             bounds1.bottom = position;
             bounds2.top = bounds1.bottom + mDividerSize;
+
+            // For flexible split, expand app offscreen as well
+            if (mDividerSnapAlgorithm.areOffscreenRatiosSupported()) {
+                if (position <= mDividerSnapAlgorithm.getMiddleTarget().position) {
+                    bounds1.top = bounds1.bottom - bounds2.width();
+                } else {
+                    bounds2.bottom = bounds2.top + bounds1.width();
+                }
+            }
         }
         DockedDividerUtils.sanitizeStackBounds(bounds1, true /** topLeft */);
         DockedDividerUtils.sanitizeStackBounds(bounds2, false /** topLeft */);
@@ -669,6 +690,21 @@
                 });
     }
 
+    /**
+     * Moves the divider to the other side of the screen. Does nothing if the divider is in the
+     * center.
+     * TODO (b/349828130): Currently only supports the two-app case. For n-apps,
+     *  DividerSnapAlgorithm will need to be refactored, and this function will change as well.
+     */
+    public void flingDividerToOtherSide(@PersistentSnapPosition int currentSnapPosition) {
+        switch (currentSnapPosition) {
+            case SNAP_TO_2_10_90 ->
+                    snapToTarget(mDividerPosition, mDividerSnapAlgorithm.getLastSplitTarget());
+            case SNAP_TO_2_90_10 ->
+                    snapToTarget(mDividerPosition, mDividerSnapAlgorithm.getFirstSplitTarget());
+        }
+    }
+
     @VisibleForTesting
     void flingDividerPosition(int from, int to, int duration,
             @Nullable Runnable flingFinishedCallback) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index bdbcb46..65bf389 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -18,6 +18,10 @@
 
 import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_10_45_45;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_45_45_10;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -38,6 +42,8 @@
 
 /** Helper utility class for split screen components to use. */
 public class SplitScreenUtils {
+    private static final int LARGE_SCREEN_MIN_EDGE_DP = 600;
+
     /** Reverse the split position. */
     @SplitScreenConstants.SplitPosition
     public static int reverseSplitPosition(@SplitScreenConstants.SplitPosition int position) {
@@ -110,10 +116,9 @@
             Configuration config) {
         // Compare the max bounds sizes as on near-square devices, the insets may result in a
         // configuration in the other orientation
-        final boolean isLargeScreen = config.smallestScreenWidthDp >= 600;
         final Rect maxBounds = config.windowConfiguration.getMaxBounds();
         final boolean isLandscape = maxBounds.width() >= maxBounds.height();
-        return isLeftRightSplit(allowLeftRightSplitInPortrait, isLargeScreen, isLandscape);
+        return isLeftRightSplit(allowLeftRightSplitInPortrait, isLargeScreen(config), isLandscape);
     }
 
     /**
@@ -128,4 +133,41 @@
             return isLandscape;
         }
     }
+
+    /**
+     * Returns whether the current config is a large screen (tablet or unfolded foldable)
+     */
+    public static boolean isLargeScreen(Configuration config) {
+        return config.smallestScreenWidthDp >= LARGE_SCREEN_MIN_EDGE_DP;
+    }
+
+    /**
+     * Returns whether we should allow split ratios to go offscreen or not. If the device is a phone
+     * or a foldable (either screen), we allow it.
+     */
+    public static boolean allowOffscreenRatios(Resources res) {
+        return !isLargeScreen(res.getConfiguration())
+                ||
+                res.getIntArray(com.android.internal.R.array.config_foldedDeviceStates).length != 0;
+    }
+
+    /**
+     * Within a particular split layout, we label the stages numerically: 0, 1, 2... from left to
+     * right (or top to bottom). This function takes in a stage index (0th, 1st, 2nd...) and a
+     * PersistentSnapPosition and returns if that particular stage is offscreen in that layout.
+     */
+    public static boolean isPartiallyOffscreen(int stageIndex,
+            @SplitScreenConstants.PersistentSnapPosition int snapPosition) {
+        switch(snapPosition) {
+            case SNAP_TO_2_10_90:
+            case SNAP_TO_3_10_45_45:
+                return stageIndex == 0;
+            case SNAP_TO_2_90_10:
+                return stageIndex == 1;
+            case SNAP_TO_3_45_45_10:
+                return stageIndex == 2;
+            default:
+                return false;
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 8e264b2..34c2f1e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -648,7 +648,13 @@
             state.startAborted = true
             // The start-transition (DRAG_HOLD) is aborted, cancel its jank interaction.
             interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
-        } else if (state.cancelTransitionToken != transition) {
+        } else if (state.cancelTransitionToken == transition) {
+            state.draggedTaskChange?.leash?.let {
+                state.startTransitionFinishTransaction?.show(it)
+            }
+            state.startTransitionFinishCb?.onTransitionFinished(null /* wct */)
+            clearState()
+        } else {
             // This transition being aborted is neither the start, nor the cancel transition, so
             // it must be the finish transition (DRAG_RELEASE); cancel its jank interaction.
             interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE)
@@ -863,7 +869,8 @@
 
     companion object {
         /** The duration of the animation to commit or cancel the drag-to-desktop gesture. */
-        internal const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L
     }
 }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index 268c3a2..537ef18 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -350,7 +350,7 @@
         }
         cancelPhysicsAnimation();
         mMenuController.hideMenu(ANIM_TYPE_DISMISS, false /* resize */);
-        // mPipTaskOrganizer.removePip();
+        mPipScheduler.removePipAfterAnimation();
     }
 
     /** Sets the movement bounds to use to constrain PIP position animations. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index d4f190e..fbbf6f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -19,81 +19,40 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
 
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.view.SurfaceControl;
 import android.window.WindowContainerTransaction;
 
-import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
 
 import com.android.internal.protolog.ProtoLog;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.pip.PipBoundsState;
-import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
 /**
  * Scheduler for Shell initiated PiP transitions and animations.
  */
 public class PipScheduler {
     private static final String TAG = PipScheduler.class.getSimpleName();
-    private static final String BROADCAST_FILTER = PipScheduler.class.getCanonicalName();
 
     private final Context mContext;
     private final PipBoundsState mPipBoundsState;
     private final ShellExecutor mMainExecutor;
     private final PipTransitionState mPipTransitionState;
-    private PipSchedulerReceiver mSchedulerReceiver;
     private PipTransitionController mPipTransitionController;
+    private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+            mSurfaceControlTransactionFactory;
 
     @Nullable private Runnable mUpdateMovementBoundsRunnable;
 
-    /**
-     * Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell.
-     * This is used for a broadcast receiver to resolve intents. This should be removed once
-     * there is an equivalent of PipTouchHandler and PipResizeGestureHandler for PiP2.
-     */
-    private static final int PIP_EXIT_VIA_EXPAND_CODE = 0;
-    private static final int PIP_DOUBLE_TAP = 1;
-
-    @IntDef(value = {
-            PIP_EXIT_VIA_EXPAND_CODE,
-            PIP_DOUBLE_TAP
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface PipUserJourneyCode {}
-
-    /**
-     * A temporary broadcast receiver to initiate PiP CUJs.
-     */
-    private class PipSchedulerReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            int userJourneyCode = intent.getIntExtra("cuj_code_extra", 0);
-            switch (userJourneyCode) {
-                case PIP_EXIT_VIA_EXPAND_CODE:
-                    scheduleExitPipViaExpand();
-                    break;
-                case PIP_DOUBLE_TAP:
-                    scheduleDoubleTapToResize();
-                    break;
-                default:
-                    throw new IllegalStateException("unexpected CUJ code=" + userJourneyCode);
-            }
-        }
-    }
-
     public PipScheduler(Context context,
             PipBoundsState pipBoundsState,
             ShellExecutor mainExecutor,
@@ -103,12 +62,8 @@
         mMainExecutor = mainExecutor;
         mPipTransitionState = pipTransitionState;
 
-        if (PipUtils.isPip2ExperimentEnabled()) {
-            // temporary broadcast receiver to initiate exit PiP via expand
-            mSchedulerReceiver = new PipSchedulerReceiver();
-            ContextCompat.registerReceiver(mContext, mSchedulerReceiver,
-                    new IntentFilter(BROADCAST_FILTER), ContextCompat.RECEIVER_EXPORTED);
-        }
+        mSurfaceControlTransactionFactory =
+                new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
     }
 
     ShellExecutor getMainExecutor() {
@@ -133,6 +88,18 @@
         return wct;
     }
 
+    @Nullable
+    private WindowContainerTransaction getRemovePipTransaction() {
+        if (mPipTransitionState.mPipTaskToken == null) {
+            return null;
+        }
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setBounds(mPipTransitionState.mPipTaskToken, null);
+        wct.setWindowingMode(mPipTransitionState.mPipTaskToken, WINDOWING_MODE_UNDEFINED);
+        wct.reorder(mPipTransitionState.mPipTaskToken, false);
+        return wct;
+    }
+
     /**
      * Schedules exit PiP via expand transition.
      */
@@ -146,10 +113,26 @@
         }
     }
 
-    /**
-     * Schedules resize PiP via double tap.
-     */
-    public void scheduleDoubleTapToResize() {}
+    // TODO: Optimize this by running the animation as part of the transition
+    /** Runs remove PiP animation and schedules remove PiP transition after the animation ends. */
+    public void removePipAfterAnimation() {
+        SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
+        PipAlphaAnimator animator = new PipAlphaAnimator(mContext,
+                mPipTransitionState.mPinnedTaskLeash, tx, PipAlphaAnimator.FADE_OUT);
+        animator.setAnimationEndCallback(this::scheduleRemovePipImmediately);
+        animator.start();
+    }
+
+    /** Schedules remove PiP transition. */
+    private void scheduleRemovePipImmediately() {
+        WindowContainerTransaction wct = getRemovePipTransaction();
+        if (wct != null) {
+            mMainExecutor.execute(() -> {
+                mPipTransitionController.startExitTransition(TRANSIT_REMOVE_PIP, wct,
+                        null /* destinationBounds */);
+            });
+        }
+    }
 
     /**
      * Animates resizing of the pinned stack given the duration.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
index c58de2c..373ec80 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
@@ -90,9 +90,10 @@
         if (mPictureInPictureParams.equals(params)) {
             return;
         }
-        if (PipUtils.remoteActionsChanged(params.getActions(), mPictureInPictureParams.getActions())
+        if (params != null && (PipUtils.remoteActionsChanged(params.getActions(),
+                mPictureInPictureParams.getActions())
                 || !PipUtils.remoteActionsMatch(params.getCloseAction(),
-                mPictureInPictureParams.getCloseAction())) {
+                mPictureInPictureParams.getCloseAction()))) {
             for (PipParamsChangedCallback listener : mPipParamsChangedListeners) {
                 listener.onActionsChanged(params.getActions(), params.getCloseAction());
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 029f001..a4a7973 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -582,7 +582,6 @@
             return true;
         }
 
-        /*
         if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting())
                 && mPipDismissTargetHandler.maybeConsumeMotionEvent(ev)) {
             // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event
@@ -599,6 +598,7 @@
             return true;
         }
 
+        /*
         // Ignore the motion event When the entry animation is waiting to be started
         if (!mTouchState.isUserInteracting() && mPipTaskOrganizer.isEntryScheduled()) {
             ProtoLog.wtf(WM_SHELL_PICTURE_IN_PICTURE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index b57f51a..ac1567a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -25,6 +25,7 @@
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP;
 
 import android.animation.Animator;
@@ -605,8 +606,11 @@
                 && pipChange.getMode() == TRANSIT_TO_BACK;
         boolean isPipClosed = info.getType() == TRANSIT_CLOSE
                 && pipChange.getMode() == TRANSIT_CLOSE;
-        // PiP is being removed if the pinned task is either moved to back or closed.
-        return isPipMovedToBack || isPipClosed;
+        // If PiP is dismissed by user (i.e. via dismiss button in PiP menu)
+        boolean isPipDismissed = info.getType() == TRANSIT_REMOVE_PIP
+                && pipChange.getMode() == TRANSIT_TO_BACK;
+        // PiP is being removed if the pinned task is either moved to back, closed, or dismissed.
+        return isPipMovedToBack || isPipClosed || isPipDismissed;
     }
 
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 47c5eec..fea987a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -35,12 +35,19 @@
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
 
 import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER;
+import static com.android.wm.shell.common.split.SplitScreenUtils.isPartiallyOffscreen;
 import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
 import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
 import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
 import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
+import static com.android.wm.shell.shared.TransitionUtil.isOrderOnly;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_1;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -117,6 +124,7 @@
 import com.android.internal.policy.FoldLockSettingsObserver;
 import com.android.internal.protolog.ProtoLog;
 import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
@@ -160,19 +168,15 @@
  * - The {@link SplitLayout} divider is only visible if multiple {@link StageTaskListener}s are
  * visible
  * - Both stages are put under a single-top root task.
- * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and
- * {@link #onStageHasChildrenChanged(StageListenerImpl).}
  */
 public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
         DisplayController.OnDisplaysChangedListener, Transitions.TransitionHandler,
-        ShellTaskOrganizer.TaskListener {
+        ShellTaskOrganizer.TaskListener, StageTaskListener.StageListenerCallbacks {
 
     private static final String TAG = StageCoordinator.class.getSimpleName();
 
     private final StageTaskListener mMainStage;
-    private final StageListenerImpl mMainStageListener = new StageListenerImpl();
     private final StageTaskListener mSideStage;
-    private final StageListenerImpl mSideStageListener = new StageListenerImpl();
     @SplitPosition
     private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
 
@@ -328,7 +332,7 @@
                 mContext,
                 mTaskOrganizer,
                 mDisplayId,
-                mMainStageListener,
+                this /*stageListenerCallbacks*/,
                 mSyncQueue,
                 iconProvider,
                 mWindowDecorViewModel);
@@ -336,7 +340,7 @@
                 mContext,
                 mTaskOrganizer,
                 mDisplayId,
-                mSideStageListener,
+                this /*stageListenerCallbacks*/,
                 mSyncQueue,
                 iconProvider,
                 mWindowDecorViewModel);
@@ -411,7 +415,7 @@
     }
 
     public boolean isSplitScreenVisible() {
-        return mSideStageListener.mVisible && mMainStageListener.mVisible;
+        return mSideStage.mVisible && mMainStage.mVisible;
     }
 
     private void activateSplit(WindowContainerTransaction wct, boolean includingTopTask) {
@@ -1096,7 +1100,7 @@
         mSideStagePosition = sideStagePosition;
         sendOnStagePositionChanged();
 
-        if (mSideStageListener.mVisible && updateBounds) {
+        if (mSideStage.mVisible && updateBounds) {
             if (wct == null) {
                 // onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
                 onLayoutSizeChanged(mSplitLayout);
@@ -1317,8 +1321,8 @@
 
     private void clearRequestIfPresented() {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearRequestIfPresented");
-        if (mSideStageListener.mVisible && mSideStageListener.mHasChildren
-                && mMainStageListener.mVisible && mSideStageListener.mHasChildren) {
+        if (mSideStage.mVisible && mSideStage.mHasChildren
+                && mMainStage.mVisible && mSideStage.mHasChildren) {
             mSplitRequest = null;
         }
     }
@@ -1571,11 +1575,12 @@
         }
     }
 
-    private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId,
+    @Override
+    public void onChildTaskStatusChanged(StageTaskListener stageListener, int taskId,
             boolean present, boolean visible) {
         int stage;
         if (present) {
-            stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
+            stage = stageListener == mSideStage ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
         } else {
             // No longer on any stage
             stage = STAGE_TYPE_UNDEFINED;
@@ -1706,13 +1711,14 @@
 
 
     @VisibleForTesting
-    void onRootTaskAppeared() {
+    @Override
+    public void onRootTaskAppeared() {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskAppeared: rootTask=%s mainRoot=%b sideRoot=%b",
-                mRootTaskInfo, mMainStageListener.mHasRootTask, mSideStageListener.mHasRootTask);
+                mRootTaskInfo, mMainStage.mHasRootTask, mSideStage.mHasRootTask);
         // Wait unit all root tasks appeared.
         if (mRootTaskInfo == null
-                || !mMainStageListener.mHasRootTask
-                || !mSideStageListener.mHasRootTask) {
+                || !mMainStage.mHasRootTask
+                || !mSideStage.mHasRootTask) {
             return;
         }
 
@@ -1732,35 +1738,8 @@
         mLaunchAdjacentController.setLaunchAdjacentRoot(mSideStage.mRootTaskInfo.token);
     }
 
-    /** Callback when split roots have child task appeared under it, this is a little different from
-     * #onStageHasChildrenChanged because this would be called every time child task appeared.
-     * NOTICE: This only be called on legacy transition. */
-    private void onChildTaskAppeared(StageListenerImpl stageListener, int taskId) {
-        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onChildTaskAppeared: isMainStage=%b task=%d",
-                stageListener == mMainStageListener, taskId);
-        // Handle entering split screen while there is a split pair running in the background.
-        if (stageListener == mSideStageListener && !isSplitScreenVisible() && isSplitActive()
-                && mSplitRequest == null) {
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
-            prepareEnterSplitScreen(wct);
-            mMainStage.evictAllChildren(wct);
-            mSideStage.evictOtherChildren(wct, taskId);
-
-            mSyncQueue.queue(wct);
-            mSyncQueue.runInSync(t -> {
-                if (mIsDropEntering) {
-                    updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
-                    mIsDropEntering = false;
-                    mSkipEvictingMainStageChildren = false;
-                } else {
-                    mShowDecorImmediately = true;
-                    mSplitLayout.flingDividerToCenter(/*finishCallback*/ null);
-                }
-            });
-        }
-    }
-
-    private void onRootTaskVanished() {
+    @Override
+    public void onRootTaskVanished() {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskVanished");
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         mLaunchAdjacentController.clearLaunchAdjacentRoot();
@@ -1777,15 +1756,16 @@
 
     /** Callback when split roots visiblility changed.
      * NOTICE: This only be called on legacy transition. */
-    private void onStageVisibilityChanged(StageListenerImpl stageListener) {
+    @Override
+    public void onStageVisibilityChanged(StageTaskListener stageListener) {
         // If split didn't active, just ignore this callback because we should already did these
         // on #applyExitSplitScreen.
         if (!isSplitActive()) {
             return;
         }
 
-        final boolean sideStageVisible = mSideStageListener.mVisible;
-        final boolean mainStageVisible = mMainStageListener.mVisible;
+        final boolean sideStageVisible = mSideStage.mVisible;
+        final boolean mainStageVisible = mMainStage.mVisible;
 
         // Wait for both stages having the same visibility to prevent causing flicker.
         if (mainStageVisible != sideStageVisible) {
@@ -1922,18 +1902,19 @@
 
     /** Callback when split roots have child or haven't under it.
      * NOTICE: This only be called on legacy transition. */
-    private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
+    @Override
+    public void onStageHasChildrenChanged(StageTaskListener stageListener) {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onStageHasChildrenChanged: isMainStage=%b",
-                stageListener == mMainStageListener);
+                stageListener == mMainStage);
         final boolean hasChildren = stageListener.mHasChildren;
-        final boolean isSideStage = stageListener == mSideStageListener;
+        final boolean isSideStage = stageListener == mSideStage;
         if (!hasChildren && !mIsExiting && isSplitActive()) {
-            if (isSideStage && mMainStageListener.mVisible) {
+            if (isSideStage && mMainStage.mVisible) {
                 // Exit to main stage if side stage no longer has children.
                 mSplitLayout.flingDividerToDismiss(
                         mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT,
                         EXIT_REASON_APP_FINISHED);
-            } else if (!isSideStage && mSideStageListener.mVisible) {
+            } else if (!isSideStage && mSideStage.mVisible) {
                 // Exit to side stage if main stage no longer has children.
                 mSplitLayout.flingDividerToDismiss(
                         mSideStagePosition != SPLIT_POSITION_BOTTOM_OR_RIGHT,
@@ -1958,7 +1939,7 @@
                 }
             });
         }
-        if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
+        if (mMainStage.mHasChildren && mSideStage.mHasChildren) {
             mShouldUpdateRecents = true;
             clearRequestIfPresented();
             updateRecentTasksSplitPair();
@@ -1974,6 +1955,35 @@
     }
 
     @Override
+    public void onNoLongerSupportMultiWindow(StageTaskListener stageTaskListener,
+            ActivityManager.RunningTaskInfo taskInfo) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onNoLongerSupportMultiWindow: task=%s", taskInfo);
+        if (isSplitActive()) {
+            final boolean isMainStage = mMainStage == stageTaskListener;
+
+            // If visible, we preserve the app and keep it running. If an app becomes
+            // unsupported in the bg, break split without putting anything on top
+            boolean splitScreenVisible = isSplitScreenVisible();
+            int stageType = STAGE_TYPE_UNDEFINED;
+            if (splitScreenVisible) {
+                stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+            }
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            prepareExitSplitScreen(stageType, wct);
+            clearSplitPairedInRecents(EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
+            mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
+                    EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
+            Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow",
+                    "app package " + taskInfo.baseIntent.getComponent()
+                            + " does not support splitscreen, or is a controlled activity"
+                            + " type"));
+            if (splitScreenVisible) {
+                handleUnsupportedSplitStart();
+            }
+        }
+    }
+
+    @Override
     public void onSnappedToDismiss(boolean bottomOrRight, @ExitReason int exitReason) {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onSnappedToDismiss: bottomOrRight=%b reason=%s",
                 bottomOrRight, exitReasonToString(exitReason));
@@ -2045,6 +2055,13 @@
             mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish");
         }, mMainStage.getSplitDecorManager(), mSideStage.getSplitDecorManager());
 
+        if (Flags.enableFlexibleTwoAppSplit()) {
+            switch (layout.calculateCurrentSnapPosition()) {
+                case SNAP_TO_2_10_90 -> grantFocusToPosition(false /* leftOrTop */);
+                case SNAP_TO_2_90_10 -> grantFocusToPosition(true /* leftOrTop */);
+            }
+        }
+
         mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
     }
 
@@ -2498,6 +2515,26 @@
                         mTaskOrganizer.applyTransaction(wct);
                     }
                     continue;
+                } else if (Flags.enableFlexibleTwoAppSplit() && isOrderOnly(change)) {
+                    int focusedStageIndex = SPLIT_INDEX_UNDEFINED;
+                    if (taskInfo.token.equals(mMainStage.mRootTaskInfo.token)) {
+                        focusedStageIndex = mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
+                                ? SPLIT_INDEX_0 : SPLIT_INDEX_1;
+                    } else if (taskInfo.token.equals(mSideStage.mRootTaskInfo.token)) {
+                        focusedStageIndex = mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
+                                ? SPLIT_INDEX_1 : SPLIT_INDEX_0;
+                    }
+
+                    if (focusedStageIndex != SPLIT_INDEX_UNDEFINED) {
+                        @PersistentSnapPosition int currentSnapPosition =
+                                mSplitLayout.calculateCurrentSnapPosition();
+                        boolean offscreenTaskFocused =
+                                isPartiallyOffscreen(focusedStageIndex, currentSnapPosition);
+
+                        if (offscreenTaskFocused) {
+                            mSplitLayout.flingDividerToOtherSide(currentSnapPosition);
+                        }
+                    }
                 }
                 final StageTaskListener stage = getStageOfTask(taskInfo);
                 if (stage == null) {
@@ -3182,13 +3219,9 @@
         pw.println(childPrefix + "stagePosition=" + splitPositionToString(getMainStagePosition()));
         pw.println(childPrefix + "isActive=" + isSplitActive());
         mMainStage.dump(pw, childPrefix);
-        pw.println(innerPrefix + "MainStageListener");
-        mMainStageListener.dump(pw, childPrefix);
         pw.println(innerPrefix + "SideStage");
         pw.println(childPrefix + "stagePosition=" + splitPositionToString(getSideStagePosition()));
         mSideStage.dump(pw, childPrefix);
-        pw.println(innerPrefix + "SideStageListener");
-        mSideStageListener.dump(pw, childPrefix);
         if (mSplitLayout != null) {
             mSplitLayout.dump(pw, childPrefix);
         }
@@ -3204,8 +3237,8 @@
      */
     private void setSplitsVisible(boolean visible) {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setSplitsVisible: visible=%b", visible);
-        mMainStageListener.mVisible = mSideStageListener.mVisible = visible;
-        mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible;
+        mMainStage.mVisible = mSideStage.mVisible = visible;
+        mMainStage.mHasChildren = mSideStage.mHasChildren = visible;
     }
 
     /**
@@ -3255,86 +3288,4 @@
                 !toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */,
                 mSplitLayout.isLeftRightSplit());
     }
-
-    class StageListenerImpl implements StageTaskListener.StageListenerCallbacks {
-        boolean mHasRootTask = false;
-        boolean mVisible = false;
-        boolean mHasChildren = false;
-
-        @Override
-        public void onRootTaskAppeared() {
-            mHasRootTask = true;
-            StageCoordinator.this.onRootTaskAppeared();
-        }
-
-        @Override
-        public void onChildTaskAppeared(int taskId) {
-            StageCoordinator.this.onChildTaskAppeared(this, taskId);
-        }
-
-        @Override
-        public void onStatusChanged(boolean visible, boolean hasChildren) {
-            if (!mHasRootTask) return;
-
-            if (mHasChildren != hasChildren) {
-                mHasChildren = hasChildren;
-                StageCoordinator.this.onStageHasChildrenChanged(this);
-            }
-            if (mVisible != visible) {
-                mVisible = visible;
-                StageCoordinator.this.onStageVisibilityChanged(this);
-            }
-        }
-
-        @Override
-        public void onChildTaskStatusChanged(int taskId, boolean present, boolean visible) {
-            StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present, visible);
-        }
-
-        @Override
-        public void onRootTaskVanished() {
-            reset();
-            StageCoordinator.this.onRootTaskVanished();
-        }
-
-        @Override
-        public void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo) {
-            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onNoLongerSupportMultiWindow: task=%s", taskInfo);
-            if (isSplitActive()) {
-                final boolean isMainStage = mMainStageListener == this;
-
-                // If visible, we preserve the app and keep it running. If an app becomes
-                // unsupported in the bg, break split without putting anything on top
-                boolean splitScreenVisible = isSplitScreenVisible();
-                int stageType = STAGE_TYPE_UNDEFINED;
-                if (splitScreenVisible) {
-                    stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
-                }
-                final WindowContainerTransaction wct = new WindowContainerTransaction();
-                prepareExitSplitScreen(stageType, wct);
-                clearSplitPairedInRecents(EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
-                mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
-                        EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
-                Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow",
-                        "app package " + taskInfo.baseIntent.getComponent()
-                                + " does not support splitscreen, or is a controlled activity"
-                                + " type"));
-                if (splitScreenVisible) {
-                    handleUnsupportedSplitStart();
-                }
-            }
-        }
-
-        private void reset() {
-            mHasRootTask = false;
-            mVisible = false;
-            mHasChildren = false;
-        }
-
-        public void dump(@NonNull PrintWriter pw, String prefix) {
-            pw.println(prefix + "mHasRootTask=" + mHasRootTask);
-            pw.println(prefix + "mVisible=" + mVisible);
-            pw.println(prefix + "mHasChildren=" + mHasChildren);
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index d64c0a2..ae4bd16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -22,20 +22,17 @@
 import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
-import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
 
 import android.annotation.CallSuper;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
-import android.util.Slog;
 import android.util.SparseArray;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
@@ -74,20 +71,21 @@
     // No current way to enforce this but if enableFlexibleSplit() is enabled, then only 1 of the
     // stages should have this be set/being used
     private boolean mIsActive;
-
     /** Callback interface for listening to changes in a split-screen stage. */
     public interface StageListenerCallbacks {
         void onRootTaskAppeared();
 
-        void onChildTaskAppeared(int taskId);
+        void onStageHasChildrenChanged(StageTaskListener stageTaskListener);
 
-        void onStatusChanged(boolean visible, boolean hasChildren);
+        void onStageVisibilityChanged(StageTaskListener stageTaskListener);
 
-        void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
+        void onChildTaskStatusChanged(StageTaskListener stage, int taskId, boolean present,
+                boolean visible);
 
         void onRootTaskVanished();
 
-        void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo);
+        void onNoLongerSupportMultiWindow(StageTaskListener stageTaskListener,
+                ActivityManager.RunningTaskInfo taskInfo);
     }
 
     private final Context mContext;
@@ -96,6 +94,12 @@
     private final IconProvider mIconProvider;
     private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
 
+    /** Whether or not the root task has been created. */
+    boolean mHasRootTask = false;
+    /** Whether or not the root task is visible. */
+    boolean mVisible = false;
+    /** Whether or not the root task has any children or not. */
+    boolean mHasChildren = false;
     protected ActivityManager.RunningTaskInfo mRootTaskInfo;
     protected SurfaceControl mRootLeash;
     protected SurfaceControl mDimLayer;
@@ -201,6 +205,7 @@
             mSplitDecorManager = new SplitDecorManager(
                     mRootTaskInfo.configuration,
                     mIconProvider);
+            mHasRootTask = true;
             mCallbacks.onRootTaskAppeared();
             sendStatusChanged();
             mSyncQueue.runInSync(t -> mDimLayer =
@@ -209,15 +214,8 @@
             final int taskId = taskInfo.taskId;
             mChildrenLeashes.put(taskId, leash);
             mChildrenTaskInfo.put(taskId, taskInfo);
-            mCallbacks.onChildTaskStatusChanged(taskId, true /* present */,
+            mCallbacks.onChildTaskStatusChanged(this, taskId, true /* present */,
                     taskInfo.isVisible && taskInfo.isVisibleRequested);
-            if (ENABLE_SHELL_TRANSITIONS) {
-                // Status is managed/synchronized by the transition lifecycle.
-                return;
-            }
-            updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
-            mCallbacks.onChildTaskAppeared(taskId);
-            sendStatusChanged();
         } else {
             throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
                     + "\n mRootTaskInfo: " + mRootTaskInfo);
@@ -231,14 +229,6 @@
                 taskInfo.taskId, taskInfo.baseActivity);
         mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo));
         if (mRootTaskInfo.taskId == taskInfo.taskId) {
-            // Inflates split decor view only when the root task is visible.
-            if (!ENABLE_SHELL_TRANSITIONS && mRootTaskInfo.isVisible != taskInfo.isVisible) {
-                if (taskInfo.isVisible) {
-                    mSplitDecorManager.inflate(mContext, mRootLeash);
-                } else {
-                    mSyncQueue.runInSync(t -> mSplitDecorManager.release(t));
-                }
-            }
             mRootTaskInfo = taskInfo;
         } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
             if (!taskInfo.supportsMultiWindow
@@ -250,25 +240,16 @@
                         taskInfo.taskId);
                 // Leave split screen if the task no longer supports multi window or have
                 // uncontrolled task.
-                mCallbacks.onNoLongerSupportMultiWindow(taskInfo);
+                mCallbacks.onNoLongerSupportMultiWindow(this, taskInfo);
                 return;
             }
             mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
-            mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
+            mCallbacks.onChildTaskStatusChanged(this, taskInfo.taskId, true /* present */,
                     taskInfo.isVisible && taskInfo.isVisibleRequested);
-            if (!ENABLE_SHELL_TRANSITIONS) {
-                updateChildTaskSurface(
-                        taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */);
-            }
         } else {
             throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
                     + "\n mRootTaskInfo: " + mRootTaskInfo);
         }
-        if (ENABLE_SHELL_TRANSITIONS) {
-            // Status is managed/synchronized by the transition lifecycle.
-            return;
-        }
-        sendStatusChanged();
     }
 
     @Override
@@ -278,6 +259,9 @@
         final int taskId = taskInfo.taskId;
         mWindowDecorViewModel.ifPresent(vm -> vm.onTaskVanished(taskInfo));
         if (mRootTaskInfo.taskId == taskId) {
+            mHasRootTask = false;
+            mVisible = false;
+            mHasChildren = false;
             mCallbacks.onRootTaskVanished();
             mRootTaskInfo = null;
             mRootLeash = null;
@@ -288,12 +272,8 @@
         } else if (mChildrenTaskInfo.contains(taskId)) {
             mChildrenTaskInfo.remove(taskId);
             mChildrenLeashes.remove(taskId);
-            mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible);
-            if (ENABLE_SHELL_TRANSITIONS) {
-                // Status is managed/synchronized by the transition lifecycle.
-                return;
-            }
-            sendStatusChanged();
+            mCallbacks.onChildTaskStatusChanged(this, taskId, false /* present */,
+                    taskInfo.isVisible);
         } else {
             throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
                     + "\n mRootTaskInfo: " + mRootTaskInfo);
@@ -455,26 +435,6 @@
         }
     }
 
-    private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo,
-            SurfaceControl leash, boolean firstAppeared) {
-        final Point taskPositionInParent = taskInfo.positionInParent;
-        mSyncQueue.runInSync(t -> {
-            // The task surface might be released before running in the sync queue for the case like
-            // trampoline launch, so check if the surface is valid before processing it.
-            if (!leash.isValid()) {
-                Slog.w(TAG, "Skip updating invalid child task surface of task#" + taskInfo.taskId);
-                return;
-            }
-            t.setCrop(leash, null);
-            t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
-            if (firstAppeared) {
-                t.setAlpha(leash, 1f);
-                t.setMatrix(leash, 1, 0, 0, 1);
-                t.show(leash);
-            }
-        });
-    }
-
     // ---------
     // Previously only used in MainStage
     boolean isActive() {
@@ -538,7 +498,19 @@
     }
 
     private void sendStatusChanged() {
-        mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0);
+        boolean hasChildren = mChildrenTaskInfo.size() > 0;
+        boolean visible = mRootTaskInfo.isVisible;
+        if (!mHasRootTask) return;
+
+        if (mHasChildren != hasChildren) {
+            mHasChildren = hasChildren;
+            mCallbacks.onStageHasChildrenChanged(this);
+        }
+
+        if (mVisible != visible) {
+            mVisible = visible;
+            mCallbacks.onStageVisibilityChanged(this);
+        }
     }
 
     @Override
@@ -554,5 +526,8 @@
                         + " baseActivity=" + taskInfo.baseActivity);
             }
         }
+        pw.println(prefix + "mHasRootTask=" + mHasRootTask);
+        pw.println(prefix + "mVisible=" + mVisible);
+        pw.println(prefix + "mHasChildren=" + mHasChildren);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index a2439a9..5437167 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -61,6 +61,7 @@
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
+import static com.android.wm.shell.transition.DefaultSurfaceAnimator.buildSurfaceAnimation;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionTypeFromInfo;
@@ -68,8 +69,6 @@
 import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
 import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -81,7 +80,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.graphics.Color;
-import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -90,12 +88,10 @@
 import android.os.IBinder;
 import android.os.UserHandle;
 import android.util.ArrayMap;
-import android.view.Choreographer;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
-import android.view.animation.Transformation;
 import android.window.TransitionInfo;
 import android.window.TransitionMetrics;
 import android.window.TransitionRequestInfo;
@@ -362,7 +358,6 @@
                     isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS;
                     if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
                         final int flags = wallpaperTransit != WALLPAPER_TRANSITION_NONE
-                                && Flags.commonSurfaceAnimator()
                                 ? ScreenRotationAnimation.FLAG_HAS_WALLPAPER : 0;
                         startRotationAnimation(startTransaction, change, info, anim, flags,
                                 animations, onAnimFinish);
@@ -823,72 +818,6 @@
         return a;
     }
 
-    /** Builds an animator for the surface and adds it to the `animations` list. */
-    static void buildSurfaceAnimation(@NonNull ArrayList<Animator> animations,
-            @NonNull Animation anim, @NonNull SurfaceControl leash,
-            @NonNull Runnable finishCallback, @NonNull TransactionPool pool,
-            @NonNull ShellExecutor mainExecutor, @Nullable Point position, float cornerRadius,
-            @Nullable Rect clipRect, boolean isActivity) {
-        if (Flags.commonSurfaceAnimator()) {
-            DefaultSurfaceAnimator.buildSurfaceAnimation(animations, anim, leash, finishCallback,
-                    pool, mainExecutor, position, cornerRadius, clipRect, isActivity);
-            return;
-        }
-        final SurfaceControl.Transaction transaction = pool.acquire();
-        final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
-        final Transformation transformation = new Transformation();
-        final float[] matrix = new float[9];
-        // Animation length is already expected to be scaled.
-        va.overrideDurationScale(1.0f);
-        va.setDuration(anim.computeDurationHint());
-        final ValueAnimator.AnimatorUpdateListener updateListener = animation -> {
-            final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
-
-            applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix,
-                    position, cornerRadius, clipRect, isActivity);
-        };
-        va.addUpdateListener(updateListener);
-
-        final Runnable finisher = () -> {
-            applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix,
-                    position, cornerRadius, clipRect, isActivity);
-
-            pool.release(transaction);
-            mainExecutor.execute(() -> {
-                animations.remove(va);
-                finishCallback.run();
-            });
-        };
-        va.addListener(new AnimatorListenerAdapter() {
-            // It is possible for the end/cancel to be called more than once, which may cause
-            // issues if the animating surface has already been released. Track the finished
-            // state here to skip duplicate callbacks. See b/252872225.
-            private boolean mFinished = false;
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                onFinish();
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                onFinish();
-            }
-
-            private void onFinish() {
-                if (mFinished) return;
-                mFinished = true;
-                finisher.run();
-                // The update listener can continue to be called after the animation has ended if
-                // end() is called manually again before the finisher removes the animation.
-                // Remove it manually here to prevent animating a released surface.
-                // See b/252872225.
-                va.removeUpdateListener(updateListener);
-            }
-        });
-        animations.add(va);
-    }
-
     private void attachThumbnail(@NonNull ArrayList<Animator> animations,
             @NonNull Runnable finishCallback, TransitionInfo.Change change,
             TransitionInfo.AnimationOptions options, float cornerRadius) {
@@ -1014,38 +943,4 @@
                 || animType == ANIM_CLIP_REVEAL || animType == ANIM_OPEN_CROSS_PROFILE_APPS
                 || animType == ANIM_FROM_STYLE;
     }
-
-    private static void applyTransformation(long time, SurfaceControl.Transaction t,
-            SurfaceControl leash, Animation anim, Transformation tmpTransformation, float[] matrix,
-            Point position, float cornerRadius, @Nullable Rect immutableClipRect,
-            boolean isActivity) {
-        tmpTransformation.clear();
-        anim.getTransformation(time, tmpTransformation);
-        if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()
-                && anim.getExtensionEdges() != 0x0 && isActivity) {
-            t.setEdgeExtensionEffect(leash, anim.getExtensionEdges());
-        }
-        if (position != null) {
-            tmpTransformation.getMatrix().postTranslate(position.x, position.y);
-        }
-        t.setMatrix(leash, tmpTransformation.getMatrix(), matrix);
-        t.setAlpha(leash, tmpTransformation.getAlpha());
-
-        final Rect clipRect = immutableClipRect == null ? null : new Rect(immutableClipRect);
-        Insets extensionInsets = Insets.min(tmpTransformation.getInsets(), Insets.NONE);
-        if (!extensionInsets.equals(Insets.NONE) && clipRect != null && !clipRect.isEmpty()) {
-            // Clip out any overflowing edge extension
-            clipRect.inset(extensionInsets);
-            t.setCrop(leash, clipRect);
-        }
-
-        if (anim.hasRoundedCorners() && cornerRadius > 0 && clipRect != null) {
-            // We can only apply rounded corner if a crop is set
-            t.setCrop(leash, clipRect);
-            t.setCornerRadius(leash, cornerRadius);
-        }
-
-        t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
-        t.apply();
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index 1a04997..6f3aa11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -21,7 +21,7 @@
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
 import static android.view.WindowManagerPolicyConstants.SCREEN_FREEZE_LAYER_BASE;
 
-import static com.android.wm.shell.transition.DefaultTransitionHandler.buildSurfaceAnimation;
+import static com.android.wm.shell.transition.DefaultSurfaceAnimator.buildSurfaceAnimation;
 import static com.android.wm.shell.transition.Transitions.TAG;
 
 import android.animation.Animator;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 1da4ef6..266e484 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -53,6 +53,7 @@
         return new BackMotionEvent(
                 /* touchX = */ touchX,
                 /* touchY = */ 0,
+                /* frameTime = */ 0,
                 /* progress = */ progress,
                 /* triggerBack = */ false,
                 /* swipeEdge = */ BackEvent.EDGE_LEFT,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
index 2235c20..2cc52c5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
@@ -221,6 +221,7 @@
         BackMotionEvent(
             /* touchX = */ touchX,
             /* touchY = */ 0f,
+            /* frameTime = */ 0,
             /* progress = */ progress,
             /* triggerBack = */ false,
             /* swipeEdge = */ BackEvent.EDGE_LEFT,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index 230f7e6..0bd3e08 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -1,5 +1,6 @@
 package com.android.wm.shell.desktopmode
 
+import android.animation.AnimatorTestRule
 import android.app.ActivityManager.RunningTaskInfo
 import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
@@ -24,6 +25,7 @@
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.Companion.DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS
 import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
 import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
 import com.android.wm.shell.splitscreen.SplitScreenController
@@ -38,6 +40,7 @@
 import junit.framework.Assert.assertTrue
 import org.junit.After
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.any
@@ -58,6 +61,9 @@
 @RunWithLooper
 @RunWith(AndroidTestingRunner::class)
 class DragToDesktopTransitionHandlerTest : ShellTestCase() {
+    @JvmField
+    @Rule
+    val mAnimatorTestRule = AnimatorTestRule(this)
 
     @Mock private lateinit var transitions: Transitions
     @Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@@ -267,16 +273,36 @@
     }
 
     @Test
-    fun cancelDragToDesktop_startWasReady_cancel() {
-        startDrag(defaultHandler)
+    fun cancelDragToDesktop_startWasReady_cancel_merged() {
+        val startToken = startDrag(defaultHandler)
 
         // Then user cancelled after it had already started.
-        defaultHandler.cancelDragToDesktopTransition(
-            DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
-        )
+        val cancelToken = cancelDragToDesktopTransition(
+            defaultHandler, DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL)
+        defaultHandler.mergeAnimation(
+            cancelToken,
+            TransitionInfo(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, 0),
+            mock<SurfaceControl.Transaction>(),
+            startToken,
+            mock<Transitions.TransitionFinishCallback>())
 
         // Cancel animation should run since it had already started.
         verify(dragAnimator).cancelAnimator()
+        assertFalse("Drag should not be in progress after cancelling", defaultHandler.inProgress)
+    }
+
+    @Test
+    fun cancelDragToDesktop_startWasReady_cancel_aborted() {
+        val startToken = startDrag(defaultHandler)
+
+        // Then user cancelled after it had already started.
+        val cancelToken = cancelDragToDesktopTransition(
+            defaultHandler, DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL)
+        defaultHandler.onTransitionConsumed(cancelToken, aborted = true, null)
+
+        // Cancel animation should run since it had already started.
+        verify(dragAnimator).cancelAnimator()
+        assertFalse("Drag should not be in progress after cancelling", defaultHandler.inProgress)
     }
 
     @Test
@@ -585,6 +611,23 @@
         return token
     }
 
+    private fun cancelDragToDesktopTransition(
+        handler: DragToDesktopTransitionHandler,
+        cancelState: DragToDesktopTransitionHandler.CancelState): IBinder {
+        val token = mock<IBinder>()
+        whenever(
+                transitions.startTransition(
+                    eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP),
+                    any(),
+                    eq(handler)
+                )
+            )
+            .thenReturn(token)
+        handler.cancelDragToDesktopTransition(cancelState)
+        mAnimatorTestRule.advanceTimeBy(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
+        return token
+    }
+
     private fun performEarlyCancel(
         handler: DragToDesktopTransitionHandler,
         cancelState: DragToDesktopTransitionHandler.CancelState
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index b7b7d0d..189684d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -23,10 +23,9 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.app.ActivityManager;
@@ -64,8 +63,6 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public final class StageTaskListenerTests extends ShellTestCase {
-    private static final boolean ENABLE_SHELL_TRANSITIONS =
-            SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
 
     @Mock
     private ShellTaskOrganizer mTaskOrganizer;
@@ -117,20 +114,20 @@
     public void testRootTaskAppeared() {
         assertThat(mStageTaskListener.mRootTaskInfo.taskId).isEqualTo(mRootTask.taskId);
         verify(mCallbacks).onRootTaskAppeared();
-        verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(false));
+        verify(mCallbacks, never()).onStageHasChildrenChanged(mStageTaskListener);
+        verify(mCallbacks, never()).onStageVisibilityChanged(mStageTaskListener);
     }
 
     @Test
-    public void testChildTaskAppeared() {
-        // With shell transitions, the transition manages status changes, so skip this test.
-        assumeFalse(ENABLE_SHELL_TRANSITIONS);
-        final ActivityManager.RunningTaskInfo childTask =
-                new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
+    public void testRootTaskVisible() {
+        mStageTaskListener.onTaskVanished(mRootTask);
+        mRootTask = new TestRunningTaskInfoBuilder().setVisible(true).build();
+        mRootTask.parentTaskId = INVALID_TASK_ID;
+        mSurfaceControl = new SurfaceControl.Builder().setName("test").build();
+        mStageTaskListener.onTaskAppeared(mRootTask, mSurfaceControl);
 
-        mStageTaskListener.onTaskAppeared(childTask, mSurfaceControl);
+        verify(mCallbacks).onStageVisibilityChanged(mStageTaskListener);
 
-        assertThat(mStageTaskListener.mChildrenTaskInfo.contains(childTask.taskId)).isTrue();
-        verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true));
     }
 
     @Test(expected = IllegalArgumentException.class)
@@ -140,29 +137,13 @@
     }
 
     @Test
-    public void testTaskVanished() {
-        // With shell transitions, the transition manages status changes, so skip this test.
-        assumeFalse(ENABLE_SHELL_TRANSITIONS);
-        final ActivityManager.RunningTaskInfo childTask =
-                new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
-        mStageTaskListener.mRootTaskInfo = mRootTask;
-        mStageTaskListener.mChildrenTaskInfo.put(childTask.taskId, childTask);
-
-        mStageTaskListener.onTaskVanished(childTask);
-        verify(mCallbacks, times(2)).onStatusChanged(eq(mRootTask.isVisible), eq(false));
-
-        mStageTaskListener.onTaskVanished(mRootTask);
-        verify(mCallbacks).onRootTaskVanished();
-    }
-
-    @Test
     public void testTaskInfoChanged_notSupportsMultiWindow() {
         final ActivityManager.RunningTaskInfo childTask =
                 new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
         childTask.supportsMultiWindow = false;
 
         mStageTaskListener.onTaskInfoChanged(childTask);
-        verify(mCallbacks).onNoLongerSupportMultiWindow(childTask);
+        verify(mCallbacks).onNoLongerSupportMultiWindow(mStageTaskListener, childTask);
     }
 
     @Test
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index cdb517b3..536afd4 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -51,6 +51,7 @@
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.Overridable;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.AttributionSource;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -1918,12 +1919,18 @@
     @Deprecated public void setSpeakerphoneOn(boolean on) {
         final IAudioService service = getService();
         try {
-            service.setSpeakerphoneOn(mICallBack, on);
+            service.setSpeakerphoneOn(mICallBack, on, getAttributionSource());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
+    private AttributionSource getAttributionSource() {
+        Context context = getContext();
+        return (context != null)
+                     ? context.getAttributionSource() : AttributionSource.myAttributionSource();
+    }
+
     /**
      * Checks whether the speakerphone is on or off.
      *
@@ -3089,7 +3096,8 @@
         final IAudioService service = getService();
         try {
             service.startBluetoothSco(mICallBack,
-                    getContext().getApplicationInfo().targetSdkVersion);
+                    getContext().getApplicationInfo().targetSdkVersion,
+                    getAttributionSource());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -3114,7 +3122,7 @@
     public void startBluetoothScoVirtualCall() {
         final IAudioService service = getService();
         try {
-            service.startBluetoothScoVirtualCall(mICallBack);
+            service.startBluetoothScoVirtualCall(mICallBack, getAttributionSource());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -3134,7 +3142,7 @@
     @Deprecated public void stopBluetoothSco() {
         final IAudioService service = getService();
         try {
-            service.stopBluetoothSco(mICallBack);
+            service.stopBluetoothSco(mICallBack,  getAttributionSource());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -9023,7 +9031,8 @@
                 Log.w(TAG, "setCommunicationDevice: device not found: " + device);
                 return false;
             }
-            return getService().setCommunicationDevice(mICallBack, device.getId());
+            return getService().setCommunicationDevice(mICallBack, device.getId(),
+                    getAttributionSource());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -9035,7 +9044,7 @@
      */
     public void clearCommunicationDevice() {
         try {
-            getService().setCommunicationDevice(mICallBack, 0);
+            getService().setCommunicationDevice(mICallBack, 0, getAttributionSource());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index 4d6ddfd..3cd5f52 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -18,7 +18,9 @@
 
 import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_ALL;
 import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_NONE;
+import static android.media.audio.Flags.FLAG_MUTED_BY_PORT_VOLUME_API;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -291,12 +293,24 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     public static final int MUTED_BY_VOLUME_SHAPER = (1 << 5);
+    /**
+     * @hide
+     * Flag used when muted by the track's port volume.
+     *
+     * <p>Note: this will replace the stream volume mute when using the AudioFlinger port volume
+     * APIs
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_MUTED_BY_PORT_VOLUME_API)
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int MUTED_BY_PORT_VOLUME = (1 << 6);
 
     /** @hide */
     @IntDef(
             flag = true,
             value = {MUTED_BY_MASTER, MUTED_BY_STREAM_VOLUME, MUTED_BY_STREAM_MUTED,
-                    MUTED_BY_APP_OPS, MUTED_BY_CLIENT_VOLUME, MUTED_BY_VOLUME_SHAPER})
+                    MUTED_BY_APP_OPS, MUTED_BY_CLIENT_VOLUME, MUTED_BY_VOLUME_SHAPER,
+                    MUTED_BY_PORT_VOLUME})
     @Retention(RetentionPolicy.SOURCE)
     public @interface PlayerMuteEvent {
     }
@@ -858,6 +872,9 @@
                 if ((mMutedState & MUTED_BY_VOLUME_SHAPER) != 0) {
                     apcToString.append("volumeShaper ");
                 }
+                if ((mMutedState & MUTED_BY_PORT_VOLUME) != 0) {
+                    apcToString.append("portVolume ");
+                }
             }
             apcToString.append(" ").append(mFormatInfo);
         }
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index a8b863b..bf09cb0 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1698,12 +1698,12 @@
     }
 
     /** @hide Wrapper for native methods called from AudioService */
-    public static int setStreamVolumeIndexAS(int stream, int index, int device) {
+    public static int setStreamVolumeIndexAS(int stream, int index, boolean muted, int device) {
         if (DEBUG_VOLUME) {
             Log.i(TAG, "setStreamVolumeIndex: " + STREAM_NAMES[stream]
-                    + " dev=" + Integer.toHexString(device) + " idx=" + index);
+                    + " dev=" + Integer.toHexString(device) + " idx=" + index + " muted=" + muted);
         }
-        return setStreamVolumeIndex(stream, index, device);
+        return setStreamVolumeIndex(stream, index, muted, device);
     }
 
     // usage for AudioRecord.startRecordingSync(), must match AudioSystem::sync_event_t
@@ -1774,7 +1774,8 @@
     @UnsupportedAppUsage
     public static native int initStreamVolume(int stream, int indexMin, int indexMax);
     @UnsupportedAppUsage
-    private static native int setStreamVolumeIndex(int stream, int index, int device);
+    private static native int setStreamVolumeIndex(int stream, int index, boolean muted,
+            int device);
     /** @hide */
     public static native int getStreamVolumeIndex(int stream, int device);
     /**
@@ -1787,7 +1788,7 @@
      * @return command completion status.
      */
     public static native int setVolumeIndexForAttributes(@NonNull AudioAttributes attributes,
-                                                         int index, int device);
+                                                         int index, boolean muted, int device);
    /**
     * @hide
     * get the volume index for the given {@link AudioAttributes}.
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 8394daf..02ca307 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -234,7 +234,7 @@
 
     int getEncodedSurroundMode(int targetSdkVersion);
 
-    void setSpeakerphoneOn(IBinder cb, boolean on);
+    void setSpeakerphoneOn(IBinder cb, boolean on, in AttributionSource attributionSource);
 
     boolean isSpeakerphoneOn();
 
@@ -263,9 +263,10 @@
 
     int getCurrentAudioFocus();
 
-    void startBluetoothSco(IBinder cb, int targetSdkVersion);
-    void startBluetoothScoVirtualCall(IBinder cb);
-    void stopBluetoothSco(IBinder cb);
+    void startBluetoothSco(IBinder cb, int targetSdkVersion,
+            in AttributionSource attributionSource);
+    void startBluetoothScoVirtualCall(IBinder cb, in AttributionSource attributionSource);
+    void stopBluetoothSco(IBinder cb, in AttributionSource attributionSource);
 
     void forceVolumeControlStream(int streamType, IBinder cb);
 
@@ -542,7 +543,7 @@
 
     int[] getAvailableCommunicationDeviceIds();
 
-    boolean setCommunicationDevice(IBinder cb, int portId);
+    boolean setCommunicationDevice(IBinder cb, int portId, in AttributionSource attributionSource);
 
     int getCommunicationDevice();
 
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
index 8c5d877..8fb16d8 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -78,6 +78,10 @@
         for (activityClass in request.activityClasses) {
             add(activityClass)
         }
+        // Temporarily add all screens
+        for (key in PreferenceScreenRegistry.preferenceScreens.keys) {
+            addPreferenceScreenFromRegistry(key, Activity::class.java)
+        }
     }
 
     fun build() = builder.build()
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
index f39f3a0..33f2dbf 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
@@ -31,6 +31,7 @@
  * information:
  * - [PreferenceTitleProvider]: provide dynamic title content
  * - [PreferenceSummaryProvider]: provide dynamic summary content (e.g. based on preference value)
+ * - [PreferenceIconProvider]: provide dynamic icon content (e.g. based on flag)
  * - [PreferenceAvailabilityProvider]: provide preference availability (e.g. based on flag)
  * - [PreferenceLifecycleProvider]: provide the lifecycle callbacks and notify state change
  *
@@ -160,6 +161,19 @@
             this is PreferenceSummaryProvider -> getSummary(context)
             else -> null
         }
+
+    /**
+     * Returns the preference icon.
+     *
+     * Implement [PreferenceIconProvider] interface if icon content is provided dynamically
+     * (e.g. icon is provided based on flag value).
+     */
+    fun getPreferenceIcon(context: Context): Int =
+        when {
+            icon != 0 -> icon
+            this is PreferenceIconProvider -> getIcon(context)
+            else -> 0
+        }
 }
 
 /** Metadata of preference group. */
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt
index 48798da..6646d6c3 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt
@@ -34,7 +34,7 @@
         ImmutableMap.of()
     }
 
-    private val preferenceScreens: PreferenceScreenMap
+    val preferenceScreens: PreferenceScreenMap
         get() = preferenceScreensSupplier.get()
 
     private var readWritePermitProvider: ReadWritePermitProvider? = null
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceStateProviders.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceStateProviders.kt
index a3aa85d..98cba1c 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceStateProviders.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceStateProviders.kt
@@ -41,6 +41,17 @@
 }
 
 /**
+ * Interface to provide dynamic preference icon.
+ *
+ * Implement this interface implies that the preference icon should not be cached for indexing.
+ */
+interface PreferenceIconProvider {
+
+    /** Provides preference icon. */
+    fun getIcon(context: Context): Int
+}
+
+/**
  * Interface to provide the state of preference availability.
  *
  * UI framework normally does not show the preference widget if it is unavailable.
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
index 5e69895..ef3d372 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
@@ -62,12 +62,13 @@
     fun bind(preference: Preference, metadata: PreferenceMetadata) {
         metadata.apply {
             preference.key = key
-            if (icon != 0) {
-                preference.setIcon(icon)
+            val context = preference.context
+            val preferenceIcon = metadata.getPreferenceIcon(context)
+            if (preferenceIcon != 0) {
+                preference.setIcon(preferenceIcon)
             } else {
                 preference.icon = null
             }
-            val context = preference.context
             val isPreferenceScreen = preference is PreferenceScreen
             preference.peekExtras()?.clear()
             extras(context)?.let { preference.extras.putAll(it) }
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index b69912a..73d0bec 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
 
 allprojects {
     extra["androidTop"] = androidTop
-    extra["jetpackComposeVersion"] = "1.7.0"
+    extra["jetpackComposeVersion"] = "1.7.3"
 }
 
 subprojects {
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 8f8275b..914f06c 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -54,7 +54,7 @@
 dependencies {
     api(project(":SettingsLibColor"))
     api("androidx.appcompat:appcompat:1.7.0")
-    api("androidx.compose.material3:material3:1.3.0")
+    api("androidx.compose.material3:material3:1.4.0-alpha01")
     api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
     api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
     api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index 2c55779..693fb35 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -46,6 +46,7 @@
 import androidx.compose.material3.TopAppBarState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.NonRestartableComposable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.derivedStateOf
@@ -326,6 +327,9 @@
     // Sets the app bar's height offset limit to hide just the bottom title area and keep top title
     // visible when collapsed.
     scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx.floatValue
+    if (isSpaExpressiveEnabled) {
+        LaunchedEffect(scrollBehavior?.state?.heightOffsetLimit) { scrollBehavior?.collapse() }
+    }
 
     // Obtain the container Color from the TopAppBarColors using the `collapsedFraction`, as the
     // bottom part of this TwoRowsTopAppBar changes color at the same rate the app bar expands or
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
index 7d8ee79..60b1e639 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
@@ -39,7 +39,6 @@
 import com.android.settingslib.spa.framework.compose.horizontalValues
 import com.android.settingslib.spa.framework.compose.verticalValues
 import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
 import com.android.settingslib.spa.framework.theme.settingsBackground
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -56,9 +55,6 @@
 ) {
     ActivityTitle(title)
     val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
-    if (isSpaExpressiveEnabled) {
-        LaunchedEffect(scrollBehavior.state.heightOffsetLimit) { scrollBehavior.collapse() }
-    }
 
     Scaffold(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 739c7d6..abc58ee 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1640,13 +1640,13 @@
     <string name="media_transfer_wired_headphone_name">Wired headphone</string>
 
     <!-- Name of the 3.5mm headphone, used in desktop devices. [CHAR LIMIT=50] -->
-    <string name="media_transfer_headphone_name">Headphone</string>
+    <string name="media_transfer_headphone_name">Wired audio</string>
 
     <!-- Name of the usb audio device speaker, used in desktop devices. [CHAR LIMIT=50] -->
     <string name="media_transfer_usb_audio_name">USB audio</string>
 
     <!-- Name of the 3.5mm audio device mic. [CHAR LIMIT=50] -->
-    <string name="media_transfer_wired_device_mic_name">Mic jack</string>
+    <string name="media_transfer_wired_device_mic_name">Wired microphone</string>
 
     <!-- Name of the usb audio device mic. [CHAR LIMIT=50] -->
     <string name="media_transfer_usb_device_mic_name">USB microphone</string>
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index 1a99d25..65b2275 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -39,6 +39,7 @@
         "configinfra_framework_flags_java_lib",
         "device_config_service_flags_java",
         "libaconfig_java_proto_lite",
+        "notification_flags_lib",
         "SettingsLibDeviceStateRotationLock",
         "SettingsLibDisplayUtils",
     ],
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 6c31831..ebeee85 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -29,6 +29,7 @@
 import android.icu.util.ULocale;
 import android.media.AudioManager;
 import android.media.RingtoneManager;
+import android.media.Utils;
 import android.net.Uri;
 import android.os.LocaleList;
 import android.os.RemoteException;
@@ -309,6 +310,13 @@
                     return SILENT_RINGTONE;
                 }
             } else {
+                // If the ringtone/notification support the vibration, use the original value.
+                final int ringtoneType = getRingtoneType(name);
+                if ((ringtoneType == RingtoneManager.TYPE_RINGTONE
+                        || ringtoneType == RingtoneManager.TYPE_NOTIFICATION)
+                        && hasVibrationSettings(value, ringtoneType)) {
+                    return value;
+                }
                 return getCanonicalRingtoneValue(value);
             }
         }
@@ -362,6 +370,15 @@
             return;
         }
 
+        // If the ringtone/notification has vibration, we backup original value in onBackupValue.
+        // So use the value directly for restoring.
+        if ((ringtoneType == RingtoneManager.TYPE_RINGTONE
+                || ringtoneType == RingtoneManager.TYPE_NOTIFICATION)
+                && hasVibrationSettings(value, ringtoneType)) {
+            RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, Uri.parse(value));
+            return;
+        }
+
         Uri ringtoneUri = null;
         try {
             ringtoneUri =
@@ -617,6 +634,19 @@
         return allLocales.remove(toFullLocale(filteredLocale));
     }
 
+    private boolean hasVibrationSettings(String value, int type) {
+        if (Utils.hasVibration(Uri.parse(value)) && mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported)) {
+            if (type == RingtoneManager.TYPE_RINGTONE) {
+                return android.media.audio.Flags.enableRingtoneHapticsCustomization();
+            }
+            if (type == RingtoneManager.TYPE_NOTIFICATION) {
+                return com.android.server.notification.Flags.notificationVibrationInSoundUri();
+            }
+        }
+        return false;
+    }
+
     /**
      * Sets the locale specified. Input data is the byte representation of comma separated
      * multiple BCP-47 language tags. For backwards compatibility, strings of the form
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
index 4b10b56..cea2bbc 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
@@ -37,9 +37,12 @@
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.media.AudioManager;
+import android.media.Utils;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.LocaleList;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.BaseColumns;
 import android.provider.MediaStore;
 import android.provider.Settings;
@@ -54,8 +57,11 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -63,6 +69,7 @@
  * Tests for the SettingsHelperTest
  */
 @RunWith(AndroidJUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
 public class SettingsHelperTest {
     private static final String SETTING_KEY = "setting_key";
     private static final String SETTING_VALUE = "setting_value";
@@ -74,9 +81,13 @@
             "content://media/internal/audio/media/20?title=DefaultNotification&canonical=1";
     private static final String DEFAULT_ALARM_VALUE =
             "content://media/internal/audio/media/30?title=DefaultAlarm&canonical=1";
+    private static final String VIBRATION_FILE_NAME = "haptics.xml";
 
     private SettingsHelper mSettingsHelper;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock private Context mContext;
     @Mock private Resources mResources;
     @Mock private AudioManager mAudioManager;
@@ -120,6 +131,22 @@
     }
 
     @Test
+    @EnableFlags({android.media.audio.Flags.FLAG_ENABLE_RINGTONE_HAPTICS_CUSTOMIZATION,
+            com.android.server.notification.Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI})
+    public void testOnBackupValue_ringtoneVibrationSupport_returnsSameValue() {
+        when(mResources.getBoolean(
+                com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported)).thenReturn(
+                true);
+        String testRingtoneVibrationValue = createUriWithVibration(DEFAULT_RINGTONE_VALUE);
+        String testNotificationVibrationValue = createUriWithVibration(DEFAULT_NOTIFICATION_VALUE);
+
+        assertEquals(testRingtoneVibrationValue, mSettingsHelper.onBackupValue(
+                Settings.System.RINGTONE, testRingtoneVibrationValue));
+        assertEquals(testNotificationVibrationValue, mSettingsHelper.onBackupValue(
+                Settings.System.NOTIFICATION_SOUND, testNotificationVibrationValue));
+    }
+
+    @Test
     public void testGetRealValue_settingNotReplaced_returnsSameValue() {
         when(mSettingsHelper.isReplacedSystemSetting(eq(SETTING_KEY))).thenReturn(false);
 
@@ -675,6 +702,30 @@
                 .isEqualTo(null);
     }
 
+    @Test
+    @EnableFlags({android.media.audio.Flags.FLAG_ENABLE_RINGTONE_HAPTICS_CUSTOMIZATION,
+            com.android.server.notification.Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI})
+    public void testRestoreValue_ringtoneVibrationSupport_restoreValue() {
+        when(mResources.getBoolean(
+                com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported)).thenReturn(
+                true);
+        String testRingtoneVibrationValue = createUriWithVibration(DEFAULT_RINGTONE_VALUE);
+        String testNotificationVibrationValue = createUriWithVibration(DEFAULT_NOTIFICATION_VALUE);
+        ContentProvider mockMediaContentProvider =
+                new MockContentProvider(mContext) {
+                    @Override
+                    public String getType(Uri url) {
+                        return "audio/ogg";
+                    }
+                };
+        mContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        resetRingtoneSettingsToDefault();
+
+        assertRingtoneSettingsRestoring(Settings.System.RINGTONE, testRingtoneVibrationValue);
+        assertRingtoneSettingsRestoring(
+                Settings.System.NOTIFICATION_SOUND, testNotificationVibrationValue);
+    }
+
     private static class MockSettingsProvider extends MockContentProvider {
         private final ArrayMap<String, String> mKeyValueStore = new ArrayMap<>();
         MockSettingsProvider(Context context) {
@@ -766,4 +817,25 @@
         assertThat(Settings.System.getString(mContentResolver, Settings.System.ALARM_ALERT))
                 .isEqualTo(DEFAULT_ALARM_VALUE);
     }
+
+    private String createUriWithVibration(String defaultUriString) {
+        return Uri.parse(defaultUriString).buildUpon()
+                .appendQueryParameter(
+                        Utils.VIBRATION_URI_PARAM, VIBRATION_FILE_NAME).build().toString();
+    }
+
+    private void assertRingtoneSettingsRestoring(
+            String settings, String testRingtoneSettingsValue) {
+        mSettingsHelper.restoreValue(
+                mContext,
+                mContentResolver,
+                new ContentValues(),
+                Uri.EMPTY,
+                settings,
+                testRingtoneSettingsValue,
+                0);
+
+        assertThat(Settings.System.getString(mContentResolver, settings))
+                .isEqualTo(testRingtoneSettingsValue);
+    }
 }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 93a99bd..18f40c9 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -136,20 +136,16 @@
             )
 
         /**
-         * The timings when animating a View into an app using a spring animator.
-         *
-         * Important: since springs don't have fixed durations, these timings represent fractions of
-         * the progress between the spring's initial value and its final value.
-         *
-         * TODO(b/372858592): make this a separate class explicitly using percentages.
+         * The timings when animating a View into an app using a spring animator. These timings
+         * represent fractions of the progress between the spring's initial value and its final
+         * value.
          */
         val SPRING_TIMINGS =
-            TransitionAnimator.Timings(
-                totalDuration = 1000L,
-                contentBeforeFadeOutDelay = 0L,
-                contentBeforeFadeOutDuration = 800L,
-                contentAfterFadeInDelay = 850L,
-                contentAfterFadeInDuration = 135L,
+            TransitionAnimator.SpringTimings(
+                contentBeforeFadeOutDelay = 0f,
+                contentBeforeFadeOutDuration = 0.8f,
+                contentAfterFadeInDelay = 0.85f,
+                contentAfterFadeInDuration = 0.135f,
             )
 
         /**
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index 1d8ff77..9dc9348 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -52,7 +52,7 @@
     private val interpolators: Interpolators,
 
     /** [springTimings] and [springInterpolators] must either both be null or both not null. */
-    private val springTimings: Timings? = null,
+    private val springTimings: SpringTimings? = null,
     private val springInterpolators: Interpolators? = null,
     private val springParams: SpringParams = DEFAULT_SPRING_PARAMS,
 ) {
@@ -83,8 +83,22 @@
             delay: Long,
             duration: Long,
         ): Float {
+            return getProgressInternal(
+                timings.totalDuration.toFloat(),
+                linearProgress,
+                delay.toFloat(),
+                duration.toFloat(),
+            )
+        }
+
+        private fun getProgressInternal(
+            totalDuration: Float,
+            linearProgress: Float,
+            delay: Float,
+            duration: Float,
+        ): Float {
             return MathUtils.constrain(
-                (linearProgress * timings.totalDuration - delay) / duration,
+                (linearProgress * totalDuration - delay) / duration,
                 0.0f,
                 1.0f,
             )
@@ -367,6 +381,25 @@
         val contentAfterFadeInDuration: Long,
     )
 
+    /**
+     * The timings (durations and delays) used by the multi-spring animator. These are expressed as
+     * fractions of 1, similar to how the progress of an animator can be expressed as a float value
+     * between 0 and 1.
+     */
+    class SpringTimings(
+        /** The portion of animation to wait before fading out the expanding content. */
+        val contentBeforeFadeOutDelay: Float,
+
+        /** The portion of animation during which the expanding content fades out. */
+        val contentBeforeFadeOutDuration: Float,
+
+        /** The portion of animation to wait before fading in the expanded content. */
+        val contentAfterFadeInDelay: Float,
+
+        /** The portion of animation during which the expanded content fades in. */
+        val contentAfterFadeInDuration: Float,
+    )
+
     /** The interpolators used by this animator. */
     data class Interpolators(
         /** The interpolator used for the Y position, width, height and corner radius. */
@@ -576,18 +609,14 @@
                 }
 
                 override fun onAnimationEnd(animation: Animator) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Animation ended")
-                    }
-
-                    // TODO(b/330672236): Post this to the main thread instead so that it does not
-                    // flicker with Flexiglass enabled.
-                    controller.onTransitionAnimationEnd(isExpandingFullyAbove)
-                    transitionContainerOverlay.remove(windowBackgroundLayer)
-
-                    if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
-                        openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
-                    }
+                    onAnimationEnd(
+                        controller,
+                        isExpandingFullyAbove,
+                        windowBackgroundLayer,
+                        transitionContainerOverlay,
+                        openingWindowSyncViewOverlay,
+                        moveBackgroundLayerWhenAppVisibilityChanges,
+                    )
                 }
             }
         )
@@ -1021,34 +1050,47 @@
         cornerRadii[7] = state.bottomCornerRadius
         drawable.cornerRadii = cornerRadii
 
-        val timings: Timings
         val interpolators: Interpolators
+        val fadeInProgress: Float
+        val fadeOutProgress: Float
         if (useSpring) {
-            timings = springTimings!!
             interpolators = springInterpolators!!
+            val timings = springTimings!!
+            fadeInProgress =
+                getProgressInternal(
+                    totalDuration = 1f,
+                    linearProgress,
+                    timings.contentBeforeFadeOutDelay,
+                    timings.contentBeforeFadeOutDuration,
+                )
+            fadeOutProgress =
+                getProgressInternal(
+                    totalDuration = 1f,
+                    linearProgress,
+                    timings.contentAfterFadeInDelay,
+                    timings.contentAfterFadeInDuration,
+                )
         } else {
-            timings = this.timings
             interpolators = this.interpolators
+            fadeInProgress =
+                getProgress(
+                    timings,
+                    linearProgress,
+                    timings.contentBeforeFadeOutDelay,
+                    timings.contentBeforeFadeOutDuration,
+                )
+            fadeOutProgress =
+                getProgress(
+                    timings,
+                    linearProgress,
+                    timings.contentAfterFadeInDelay,
+                    timings.contentAfterFadeInDuration,
+                )
         }
 
         // We first fade in the background layer to hide the expanding view, then fade it out with
         // SRC mode to draw a hole punch in the status bar and reveal the opening window (if
         // needed). If !isLaunching, the reverse happens.
-        val fadeInProgress =
-            getProgress(
-                timings,
-                linearProgress,
-                timings.contentBeforeFadeOutDelay,
-                timings.contentBeforeFadeOutDuration,
-            )
-        val fadeOutProgress =
-            getProgress(
-                timings,
-                linearProgress,
-                timings.contentAfterFadeInDelay,
-                timings.contentAfterFadeInDuration,
-            )
-
         if (isLaunching) {
             if (fadeInProgress < 1) {
                 val alpha =
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
index a525f36..9390664 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -30,14 +30,10 @@
 import androidx.compose.ui.viewinterop.AndroidView
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
-import com.android.keyguard.LockIconView
-import com.android.keyguard.LockIconViewController
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
 import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
 import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
@@ -61,7 +57,6 @@
     private val windowManager: WindowManager,
     private val authController: AuthController,
     private val featureFlags: FeatureFlagsClassic,
-    private val lockIconViewController: Lazy<LockIconViewController>,
     private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>,
     private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>,
     private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
@@ -71,42 +66,28 @@
 ) {
     @Composable
     fun SceneScope.LockIcon(overrideColor: Color? = null, modifier: Modifier = Modifier) {
-        if (!KeyguardBottomAreaRefactor.isEnabled && !DeviceEntryUdfpsRefactor.isEnabled) {
-            return
-        }
-
         val context = LocalContext.current
 
         AndroidView(
             factory = { context ->
-                val view =
-                    if (DeviceEntryUdfpsRefactor.isEnabled) {
-                        DeviceEntryIconView(
-                                context,
-                                null,
-                                logger = LongPressHandlingViewLogger(logBuffer, tag = TAG)
-                            )
-                            .apply {
-                                id = R.id.device_entry_icon_view
-                                DeviceEntryIconViewBinder.bind(
-                                    applicationScope,
-                                    this,
-                                    deviceEntryIconViewModel.get(),
-                                    deviceEntryForegroundViewModel.get(),
-                                    deviceEntryBackgroundViewModel.get(),
-                                    falsingManager.get(),
-                                    vibratorHelper.get(),
-                                    overrideColor,
-                                )
-                            }
-                    } else {
-                        // KeyguardBottomAreaRefactor.isEnabled
-                        LockIconView(context, null).apply {
-                            id = R.id.lock_icon_view
-                            lockIconViewController.get().setLockIconView(this)
-                        }
+                DeviceEntryIconView(
+                        context,
+                        null,
+                        logger = LongPressHandlingViewLogger(logBuffer, tag = TAG),
+                    )
+                    .apply {
+                        id = R.id.device_entry_icon_view
+                        DeviceEntryIconViewBinder.bind(
+                            applicationScope,
+                            this,
+                            deviceEntryIconViewModel.get(),
+                            deviceEntryForegroundViewModel.get(),
+                            deviceEntryBackgroundViewModel.get(),
+                            falsingManager.get(),
+                            vibratorHelper.get(),
+                            overrideColor,
+                        )
                     }
-                view
             },
             modifier =
                 modifier.element(LockIconElementKey).layout { measurable, _ ->
@@ -141,9 +122,7 @@
      * On devices that support UDFPS (under-display fingerprint sensor), the bounds of the icon are
      * the same as the bounds of the sensor.
      */
-    private fun lockIconBounds(
-        context: Context,
-    ): IntRect {
+    private fun lockIconBounds(context: Context): IntRect {
         val windowViewBounds = windowManager.currentWindowMetrics.bounds
         var widthPx = windowViewBounds.right.toFloat()
         if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) {
@@ -162,10 +141,7 @@
         val (center, radius) =
             if (authController.isUdfpsSupported && udfpsLocation != null) {
                 Pair(
-                    IntOffset(
-                        x = udfpsLocation.x,
-                        y = udfpsLocation.y,
-                    ),
+                    IntOffset(x = udfpsLocation.x, y = udfpsLocation.y),
                     authController.udfpsRadius.toInt(),
                 )
             } else {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt
index 2e39524..73c4fab 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt
@@ -33,7 +33,7 @@
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.padding
-import com.android.systemui.customization.R as customizationR
+import com.android.systemui.customization.R as customR
 import com.android.systemui.keyguard.ui.composable.blueprint.WeatherClockElementKeys
 import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
@@ -57,12 +57,12 @@
         Row(
             modifier =
                 Modifier.padding(
-                        horizontal = dimensionResource(customizationR.dimen.clock_padding_start)
+                        horizontal = dimensionResource(customR.dimen.clock_padding_start)
                     )
                     .burnInAware(aodBurnInViewModel, burnInParams, isClock = true)
         ) {
             WeatherElement(
-                weatherClockElementViewId = customizationR.id.weather_clock_time,
+                weatherClockElementViewId = customR.id.weather_clock_time,
                 clock = clock,
                 elementKey = WeatherClockElementKeys.timeElementKey,
             )
@@ -75,7 +75,7 @@
         modifier: Modifier = Modifier,
     ) {
         WeatherElement(
-            weatherClockElementViewId = customizationR.id.weather_clock_date,
+            weatherClockElementViewId = customR.id.weather_clock_date,
             clock = clock,
             elementKey = WeatherClockElementKeys.dateElementKey,
             modifier = modifier,
@@ -88,7 +88,7 @@
         modifier: Modifier = Modifier,
     ) {
         WeatherElement(
-            weatherClockElementViewId = customizationR.id.weather_clock_weather_icon,
+            weatherClockElementViewId = customR.id.weather_clock_weather_icon,
             clock = clock,
             elementKey = WeatherClockElementKeys.weatherIconElementKey,
             modifier = modifier.wrapContentSize(),
@@ -101,7 +101,7 @@
         modifier: Modifier = Modifier,
     ) {
         WeatherElement(
-            weatherClockElementViewId = customizationR.id.weather_clock_alarm_dnd,
+            weatherClockElementViewId = customR.id.weather_clock_alarm_dnd,
             clock = clock,
             elementKey = WeatherClockElementKeys.dndAlarmElementKey,
             modifier = modifier.wrapContentSize(),
@@ -114,7 +114,7 @@
         modifier: Modifier = Modifier,
     ) {
         WeatherElement(
-            weatherClockElementViewId = customizationR.id.weather_clock_temperature,
+            weatherClockElementViewId = customR.id.weather_clock_temperature,
             clock = clock,
             elementKey = WeatherClockElementKeys.temperatureElementKey,
             modifier = modifier.wrapContentSize(),
@@ -159,7 +159,7 @@
             modifier =
                 Modifier.height(IntrinsicSize.Max)
                     .padding(
-                        horizontal = dimensionResource(customizationR.dimen.clock_padding_start)
+                        horizontal = dimensionResource(customR.dimen.clock_padding_start)
                     )
                     .burnInAware(aodBurnInViewModel, burnInParams, isClock = true)
         ) {
@@ -168,7 +168,7 @@
                 modifier =
                     Modifier.fillMaxSize()
                         .padding(
-                            start = dimensionResource(customizationR.dimen.clock_padding_start)
+                            start = dimensionResource(customR.dimen.clock_padding_start)
                         )
             ) {
                 Weather(clock = clock, modifier = Modifier.align(Alignment.TopStart))
diff --git a/packages/SystemUI/customization/res/values/ids.xml b/packages/SystemUI/customization/res/values/ids.xml
index ec466f0..3a3e06b 100644
--- a/packages/SystemUI/customization/res/values/ids.xml
+++ b/packages/SystemUI/customization/res/values/ids.xml
@@ -1,5 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
+    <item type="id" name="lockscreen_clock_view_large" />
+    <item type="id" name="lockscreen_clock_view" />
+
     <!-- View ids for elements in large weather clock -->
     <item type="id" name="weather_clock_time" />
     <item type="id" name="weather_clock_date" />
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
index 9b94c91..eedddb2 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
@@ -30,16 +30,16 @@
 import com.android.systemui.plugins.clocks.ClockReactiveSetting
 import com.android.systemui.plugins.clocks.WeatherData
 import com.android.systemui.plugins.clocks.ZenData
-import com.android.systemui.shared.clocks.view.DigitalClockFaceView
 import com.android.systemui.shared.clocks.view.FlexClockView
+import com.android.systemui.shared.clocks.view.SimpleDigitalClockTextView
 import java.util.Locale
 import java.util.TimeZone
 
 class ComposedDigitalLayerController(
     private val ctx: Context,
-    private val assets: AssetLoader,
+    private val resources: Resources,
+    private val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources
     private val layer: ComposedDigitalHandLayer,
-    private val isLargeClock: Boolean,
     messageBuffer: MessageBuffer,
 ) : SimpleClockLayerController {
     private val logger = Logger(messageBuffer, ComposedDigitalLayerController::class.simpleName!!)
@@ -48,34 +48,22 @@
     val dozeState = DefaultClockController.AnimationState(1F)
     var isRegionDark = true
 
-    override var view: DigitalClockFaceView =
-        when (layer.customizedView) {
-            "FlexClockView" -> FlexClockView(ctx, assets, messageBuffer)
-            else -> {
-                throw IllegalStateException("CustomizedView string is not valid")
-            }
-        }
-
-    // Matches LayerControllerConstructor
-    internal constructor(
-        ctx: Context,
-        assets: AssetLoader,
-        layer: ClockLayer,
-        isLargeClock: Boolean,
-        messageBuffer: MessageBuffer,
-    ) : this(ctx, assets, layer as ComposedDigitalHandLayer, isLargeClock, messageBuffer)
+    override val view = FlexClockView(ctx, assets, messageBuffer)
 
     init {
         layer.digitalLayers.forEach {
+            val childView = SimpleDigitalClockTextView(ctx, messageBuffer)
             val controller =
-                SimpleClockLayerController.Factory.create(
+                SimpleDigitalHandLayerController(
                     ctx,
+                    resources,
                     assets,
-                    it,
-                    isLargeClock,
+                    it as DigitalHandLayer,
+                    childView,
                     messageBuffer,
                 )
-            view.addView(controller.view)
+
+            view.addView(childView)
             layerControllers.add(controller)
         }
     }
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 ac26842..3903dba 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
@@ -53,11 +53,8 @@
         }
 
         return if (clockReactiveVariants) {
-            // TODO handle the case here where only the smallClock message buffer is added
-            val assetLoader =
-                AssetLoader(ctx, ctx, "clocks/", messageBuffers?.smallClockMessageBuffer!!)
-
-            SimpleClockController(ctx, assetLoader, FLEX_DESIGN, messageBuffers)
+            val assets = AssetLoader(ctx, ctx, "clocks/", messageBuffers!!.infraMessageBuffer)
+            FlexClockController(ctx, resources, assets, FLEX_DESIGN, messageBuffers)
         } else {
             DefaultClockController(
                 ctx,
@@ -82,6 +79,9 @@
             resources.getString(R.string.clock_default_description),
             // TODO(b/352049256): Update placeholder to actual resource
             resources.getDrawable(R.drawable.clock_default_thumbnail, null),
+            isReactiveToTone = true,
+            isReactiveToTouch = clockReactiveVariants,
+            axes = listOf(), // TODO: Ater some picker definition
         )
     }
 
@@ -118,9 +118,9 @@
                                     alignment =
                                         DigitalAlignment(
                                             HorizontalAlignment.CENTER,
-                                            VerticalAlignment.CENTER
+                                            VerticalAlignment.CENTER,
                                         ),
-                                    dateTimeFormat = "hh"
+                                    dateTimeFormat = "hh",
                                 ),
                                 DigitalHandLayer(
                                     layerBounds = LayerBounds.FIT,
@@ -146,9 +146,9 @@
                                     alignment =
                                         DigitalAlignment(
                                             HorizontalAlignment.CENTER,
-                                            VerticalAlignment.CENTER
+                                            VerticalAlignment.CENTER,
                                         ),
-                                    dateTimeFormat = "hh"
+                                    dateTimeFormat = "hh",
                                 ),
                                 DigitalHandLayer(
                                     layerBounds = LayerBounds.FIT,
@@ -174,9 +174,9 @@
                                     alignment =
                                         DigitalAlignment(
                                             HorizontalAlignment.CENTER,
-                                            VerticalAlignment.CENTER
+                                            VerticalAlignment.CENTER,
                                         ),
-                                    dateTimeFormat = "mm"
+                                    dateTimeFormat = "mm",
                                 ),
                                 DigitalHandLayer(
                                     layerBounds = LayerBounds.FIT,
@@ -202,11 +202,11 @@
                                     alignment =
                                         DigitalAlignment(
                                             HorizontalAlignment.CENTER,
-                                            VerticalAlignment.CENTER
+                                            VerticalAlignment.CENTER,
                                         ),
-                                    dateTimeFormat = "mm"
-                                )
-                            )
+                                    dateTimeFormat = "mm",
+                                ),
+                            ),
                     )
                 )
 
@@ -230,7 +230,7 @@
                                 renderType = RenderType.CHANGE_WEIGHT,
                             ),
                         alignment = DigitalAlignment(HorizontalAlignment.LEFT, null),
-                        dateTimeFormat = "h:mm"
+                        dateTimeFormat = "h:mm",
                     )
                 )
 
@@ -239,7 +239,7 @@
                 name = "@string/clock_default_name",
                 description = "@string/clock_default_description",
                 large = ClockFace(layers = largeLayer),
-                small = ClockFace(layers = smallLayer)
+                small = ClockFace(layers = smallLayer),
             )
         }
     }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
similarity index 82%
rename from packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockController.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
index ec77798..b8ebd0f 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
@@ -18,7 +18,7 @@
 
 import android.content.Context
 import android.content.res.Resources
-import com.android.systemui.monet.Style as MonetStyle
+import com.android.systemui.customization.R
 import com.android.systemui.plugins.clocks.AlarmData
 import com.android.systemui.plugins.clocks.ClockConfig
 import com.android.systemui.plugins.clocks.ClockController
@@ -27,21 +27,24 @@
 import com.android.systemui.plugins.clocks.ClockReactiveSetting
 import com.android.systemui.plugins.clocks.WeatherData
 import com.android.systemui.plugins.clocks.ZenData
+import com.android.systemui.shared.clocks.view.FlexClockView
 import java.io.PrintWriter
 import java.util.Locale
 import java.util.TimeZone
 
-/** Controller for a simple json specified clock */
-class SimpleClockController(
+/** Controller for the default flex clock */
+class FlexClockController(
     private val ctx: Context,
-    private val assets: AssetLoader,
-    val design: ClockDesign,
+    private val resources: Resources,
+    private val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources
+    val design: ClockDesign, // TODO(b/364680879): Remove when done inlining
     val messageBuffers: ClockMessageBuffers?,
 ) : ClockController {
     override val smallClock = run {
         val buffer = messageBuffers?.smallClockMessageBuffer ?: LogUtil.DEFAULT_MESSAGE_BUFFER
-        SimpleClockFaceController(
+        FlexClockFaceController(
             ctx,
+            resources,
             assets.copy(messageBuffer = buffer),
             design.small ?: design.large!!,
             false,
@@ -51,8 +54,9 @@
 
     override val largeClock = run {
         val buffer = messageBuffers?.largeClockMessageBuffer ?: LogUtil.DEFAULT_MESSAGE_BUFFER
-        SimpleClockFaceController(
+        FlexClockFaceController(
             ctx,
+            resources,
             assets.copy(messageBuffer = buffer),
             design.large ?: design.small!!,
             true,
@@ -62,16 +66,10 @@
 
     override val config: ClockConfig by lazy {
         ClockConfig(
-            design.id,
-            design.name?.let { assets.tryReadString(it) ?: it } ?: "",
-            design.description?.let { assets.tryReadString(it) ?: it } ?: "",
-            isReactiveToTone =
-                design.colorPalette == null || design.colorPalette == MonetStyle.CLOCK,
-            useAlternateSmartspaceAODTransition =
-                smallClock.config.hasCustomWeatherDataDisplay ||
-                    largeClock.config.hasCustomWeatherDataDisplay,
-            useCustomClockScene =
-                smallClock.config.useCustomClockScene || largeClock.config.useCustomClockScene,
+            DEFAULT_CLOCK_ID,
+            resources.getString(R.string.clock_default_name),
+            resources.getString(R.string.clock_default_description),
+            isReactiveToTone = true,
         )
     }
 
@@ -80,8 +78,8 @@
             override var isReactiveTouchInteractionEnabled = false
                 set(value) {
                     field = value
-                    smallClock.events.isReactiveTouchInteractionEnabled = value
-                    largeClock.events.isReactiveTouchInteractionEnabled = value
+                    val view = largeClock.view as FlexClockView
+                    view.isReactiveTouchInteractionEnabled = value
                 }
 
             override fun onTimeZoneChanged(timeZone: TimeZone) {
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
new file mode 100644
index 0000000..ef24d2a
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
@@ -0,0 +1,261 @@
+/*
+ * 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.shared.clocks
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Rect
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.widget.FrameLayout
+import com.android.systemui.customization.R
+import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.plugins.clocks.AlarmData
+import com.android.systemui.plugins.clocks.ClockAnimations
+import com.android.systemui.plugins.clocks.ClockEvents
+import com.android.systemui.plugins.clocks.ClockFaceConfig
+import com.android.systemui.plugins.clocks.ClockFaceController
+import com.android.systemui.plugins.clocks.ClockFaceEvents
+import com.android.systemui.plugins.clocks.ClockFaceLayout
+import com.android.systemui.plugins.clocks.ClockReactiveSetting
+import com.android.systemui.plugins.clocks.DefaultClockFaceLayout
+import com.android.systemui.plugins.clocks.WeatherData
+import com.android.systemui.plugins.clocks.ZenData
+import com.android.systemui.shared.clocks.view.FlexClockView
+import com.android.systemui.shared.clocks.view.SimpleDigitalClockTextView
+import java.util.Locale
+import java.util.TimeZone
+import kotlin.math.max
+
+// TODO(b/364680879): Merge w/ ComposedDigitalLayerController
+class FlexClockFaceController(
+    ctx: Context,
+    private val resources: Resources,
+    val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources
+    face: ClockFace,
+    private val isLargeClock: Boolean,
+    messageBuffer: MessageBuffer,
+) : ClockFaceController {
+    override val view: View
+        get() = layerController.view
+
+    override val config =
+        ClockFaceConfig(
+            hasCustomPositionUpdatedAnimation = false // TODO(b/364673982)
+        )
+
+    private val keyguardLargeClockTopMargin =
+        resources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin)
+    val layerController: SimpleClockLayerController
+    val timespecHandler = DigitalTimespecHandler(DigitalTimespec.TIME_FULL_FORMAT, "hh:mm")
+
+    init {
+        val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+        lp.gravity = Gravity.CENTER
+
+        val layer = face.layers[0]
+
+        layerController =
+            if (isLargeClock)
+                ComposedDigitalLayerController(
+                    ctx,
+                    resources,
+                    assets,
+                    layer as ComposedDigitalHandLayer,
+                    messageBuffer,
+                )
+            else {
+                val childView = SimpleDigitalClockTextView(ctx, messageBuffer)
+                SimpleDigitalHandLayerController(
+                    ctx,
+                    resources,
+                    assets,
+                    layer as DigitalHandLayer,
+                    childView,
+                    messageBuffer,
+                )
+            }
+        layerController.view.layoutParams = lp
+    }
+
+    override val layout: ClockFaceLayout =
+        DefaultClockFaceLayout(view).apply {
+            views[0].id =
+                if (isLargeClock) R.id.lockscreen_clock_view_large else R.id.lockscreen_clock_view
+        }
+
+    override val events = FlexClockFaceEvents()
+
+    // TODO(b/364680879): Remove ClockEvents
+    inner class FlexClockFaceEvents : ClockEvents, ClockFaceEvents {
+        override var isReactiveTouchInteractionEnabled = false
+            get() = field
+            set(value) {
+                field = value
+                layerController.events.isReactiveTouchInteractionEnabled = value
+            }
+
+        override fun onTimeTick() {
+            timespecHandler.updateTime()
+            view.contentDescription = timespecHandler.getContentDescription()
+            layerController.faceEvents.onTimeTick()
+        }
+
+        override fun onTimeZoneChanged(timeZone: TimeZone) {
+            timespecHandler.timeZone = timeZone
+            layerController.events.onTimeZoneChanged(timeZone)
+        }
+
+        override fun onTimeFormatChanged(is24Hr: Boolean) {
+            timespecHandler.is24Hr = is24Hr
+            layerController.events.onTimeFormatChanged(is24Hr)
+        }
+
+        override fun onLocaleChanged(locale: Locale) {
+            timespecHandler.updateLocale(locale)
+            layerController.events.onLocaleChanged(locale)
+        }
+
+        override fun onFontSettingChanged(fontSizePx: Float) {
+            layerController.faceEvents.onFontSettingChanged(fontSizePx)
+        }
+
+        override fun onColorPaletteChanged(resources: Resources) {
+            layerController.events.onColorPaletteChanged(resources)
+            layerController.updateColors()
+        }
+
+        override fun onSeedColorChanged(seedColor: Int?) {
+            layerController.events.onSeedColorChanged(seedColor)
+            layerController.updateColors()
+        }
+
+        override fun onRegionDarknessChanged(isRegionDark: Boolean) {
+            layerController.faceEvents.onRegionDarknessChanged(isRegionDark)
+        }
+
+        override fun onReactiveAxesChanged(axes: List<ClockReactiveSetting>) {}
+
+        /**
+         * targetRegion passed to all customized clock applies counter translationY of
+         * KeyguardStatusView 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
+            // it needs to offset back the translation of keyguard_large_clock_top_margin
+            if (isLargeClock && (view as FlexClockView).isAlignedWithScreen()) {
+                val topMargin = keyguardLargeClockTopMargin
+                targetRegion?.let {
+                    val (_, yDiff) = computeLayoutDiff(view, it, isLargeClock)
+                    // In LS, we use yDiff to counter translate
+                    // the translation of KeyguardLargeClockTopMargin
+                    // With the targetRegion passed from picker,
+                    // we will have yDiff = 0, no translation is needed for weather clock
+                    if (yDiff.toInt() != 0) view.translationY = yDiff - topMargin / 2
+                }
+                return
+            }
+
+            var maxWidth = 0f
+            var maxHeight = 0f
+
+            layerController.faceEvents.onTargetRegionChanged(targetRegion)
+            maxWidth = max(maxWidth, view.layoutParams.width.toFloat())
+            maxHeight = max(maxHeight, view.layoutParams.height.toFloat())
+
+            val lp =
+                if (maxHeight <= 0 || maxWidth <= 0 || targetRegion == null) {
+                    // No specified width/height. Just match parent size.
+                    FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+                } else {
+                    // Scale to fit in targetRegion based on largest child elements.
+                    val ratio = maxWidth / maxHeight
+                    val targetRatio = targetRegion.width() / targetRegion.height().toFloat()
+                    val scale =
+                        if (ratio > targetRatio) targetRegion.width() / maxWidth
+                        else targetRegion.height() / maxHeight
+
+                    FrameLayout.LayoutParams(
+                        (maxWidth * scale).toInt(),
+                        (maxHeight * scale).toInt(),
+                    )
+                }
+
+            lp.gravity = Gravity.CENTER
+            view.layoutParams = lp
+            targetRegion?.let {
+                val (xDiff, yDiff) = computeLayoutDiff(view, it, isLargeClock)
+                view.translationX = xDiff
+                view.translationY = yDiff
+            }
+        }
+
+        override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {}
+
+        override fun onWeatherDataChanged(data: WeatherData) {
+            layerController.events.onWeatherDataChanged(data)
+        }
+
+        override fun onAlarmDataChanged(data: AlarmData) {
+            layerController.events.onAlarmDataChanged(data)
+        }
+
+        override fun onZenDataChanged(data: ZenData) {
+            layerController.events.onZenDataChanged(data)
+        }
+    }
+
+    override val animations =
+        object : ClockAnimations {
+            override fun enter() {
+                layerController.animations.enter()
+            }
+
+            override fun doze(fraction: Float) {
+                layerController.animations.doze(fraction)
+            }
+
+            override fun fold(fraction: Float) {
+                layerController.animations.fold(fraction)
+            }
+
+            override fun charge() {
+                layerController.animations.charge()
+            }
+
+            override fun onPickerCarouselSwiping(swipingFraction: Float) {
+                face.pickerScale?.let {
+                    view.scaleX = swipingFraction * (1 - it.scaleX) + it.scaleX
+                    view.scaleY = swipingFraction * (1 - it.scaleY) + it.scaleY
+                }
+                if (isLargeClock && !(view as FlexClockView).isAlignedWithScreen()) {
+                    view.translationY = keyguardLargeClockTopMargin / 2F * swipingFraction
+                }
+                layerController.animations.onPickerCarouselSwiping(swipingFraction)
+                view.invalidate()
+            }
+
+            override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {
+                layerController.animations.onPositionUpdated(fromLeft, direction, fraction)
+            }
+
+            override fun onPositionUpdated(distance: Float, fraction: Float) {
+                layerController.animations.onPositionUpdated(distance, fraction)
+            }
+        }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockFaceController.kt
deleted file mode 100644
index ef398d1..0000000
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockFaceController.kt
+++ /dev/null
@@ -1,314 +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 com.android.systemui.shared.clocks
-
-import android.content.Context
-import android.content.res.Resources
-import android.graphics.Rect
-import android.view.Gravity
-import android.view.View
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.widget.FrameLayout
-import com.android.systemui.log.core.MessageBuffer
-import com.android.systemui.plugins.clocks.AlarmData
-import com.android.systemui.plugins.clocks.ClockAnimations
-import com.android.systemui.plugins.clocks.ClockEvents
-import com.android.systemui.plugins.clocks.ClockFaceConfig
-import com.android.systemui.plugins.clocks.ClockFaceController
-import com.android.systemui.plugins.clocks.ClockFaceEvents
-import com.android.systemui.plugins.clocks.ClockFaceLayout
-import com.android.systemui.plugins.clocks.ClockReactiveSetting
-import com.android.systemui.plugins.clocks.ClockTickRate
-import com.android.systemui.plugins.clocks.DefaultClockFaceLayout
-import com.android.systemui.plugins.clocks.WeatherData
-import com.android.systemui.plugins.clocks.ZenData
-import com.android.systemui.shared.clocks.view.DigitalClockFaceView
-import java.util.Locale
-import java.util.TimeZone
-import kotlin.math.max
-
-interface ClockEventUnion : ClockEvents, ClockFaceEvents
-
-class SimpleClockFaceController(
-    ctx: Context,
-    val assets: AssetLoader,
-    face: ClockFace,
-    isLargeClock: Boolean,
-    messageBuffer: MessageBuffer,
-) : ClockFaceController {
-    override val view: View
-    override val config: ClockFaceConfig by lazy {
-        ClockFaceConfig(
-            hasCustomWeatherDataDisplay = layers.any { it.config.hasCustomWeatherDataDisplay },
-            hasCustomPositionUpdatedAnimation =
-                layers.any { it.config.hasCustomPositionUpdatedAnimation },
-            tickRate = getTickRate(),
-            useCustomClockScene = layers.any { it.config.useCustomClockScene },
-        )
-    }
-
-    val layers = mutableListOf<SimpleClockLayerController>()
-
-    val timespecHandler = DigitalTimespecHandler(DigitalTimespec.TIME_FULL_FORMAT, "hh:mm")
-
-    init {
-        val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
-        lp.gravity = Gravity.CENTER
-        view =
-            if (face.layers.size == 1) {
-                // Optimize a clocks with a single layer by excluding the face level view group. We
-                // expect the view container from the host process to always be a FrameLayout.
-                val layer = face.layers[0]
-                val controller =
-                    SimpleClockLayerController.Factory.create(
-                        ctx,
-                        assets,
-                        layer,
-                        isLargeClock,
-                        messageBuffer,
-                    )
-                layers.add(controller)
-                controller.view.layoutParams = lp
-                controller.view
-            } else {
-                // For multiple views, we use an intermediate RelativeLayout so that we can do some
-                // intelligent laying out between the children views.
-                val group = SimpleClockRelativeLayout(ctx, face.faceLayout)
-                group.layoutParams = lp
-                group.gravity = Gravity.CENTER
-                group.clipChildren = false
-                for (layer in face.layers) {
-                    face.faceLayout?.let {
-                        if (layer is DigitalHandLayer) {
-                            layer.faceLayout = it
-                        }
-                    }
-                    val controller =
-                        SimpleClockLayerController.Factory.create(
-                            ctx,
-                            assets,
-                            layer,
-                            isLargeClock,
-                            messageBuffer,
-                        )
-                    group.addView(controller.view)
-                    layers.add(controller)
-                }
-                group
-            }
-    }
-
-    override val layout: ClockFaceLayout =
-        DefaultClockFaceLayout(view).apply {
-            views[0].id =
-                if (isLargeClock) {
-                    assets.getResourcesId("lockscreen_clock_view_large")
-                } else {
-                    assets.getResourcesId("lockscreen_clock_view")
-                }
-        }
-
-    override val events =
-        object : ClockEventUnion {
-            override var isReactiveTouchInteractionEnabled = false
-                get() = field
-                set(value) {
-                    field = value
-                    layers.forEach { it.events.isReactiveTouchInteractionEnabled = value }
-                }
-
-            override fun onTimeTick() {
-                timespecHandler.updateTime()
-                if (
-                    config.tickRate == ClockTickRate.PER_MINUTE ||
-                        view.contentDescription != timespecHandler.getContentDescription()
-                ) {
-                    view.contentDescription = timespecHandler.getContentDescription()
-                }
-                layers.forEach { it.faceEvents.onTimeTick() }
-            }
-
-            override fun onTimeZoneChanged(timeZone: TimeZone) {
-                timespecHandler.timeZone = timeZone
-                layers.forEach { it.events.onTimeZoneChanged(timeZone) }
-            }
-
-            override fun onTimeFormatChanged(is24Hr: Boolean) {
-                timespecHandler.is24Hr = is24Hr
-                layers.forEach { it.events.onTimeFormatChanged(is24Hr) }
-            }
-
-            override fun onLocaleChanged(locale: Locale) {
-                timespecHandler.updateLocale(locale)
-                layers.forEach { it.events.onLocaleChanged(locale) }
-            }
-
-            override fun onFontSettingChanged(fontSizePx: Float) {
-                layers.forEach { it.faceEvents.onFontSettingChanged(fontSizePx) }
-            }
-
-            override fun onColorPaletteChanged(resources: Resources) {
-                layers.forEach {
-                    it.events.onColorPaletteChanged(resources)
-                    it.updateColors()
-                }
-            }
-
-            override fun onSeedColorChanged(seedColor: Int?) {
-                layers.forEach {
-                    it.events.onSeedColorChanged(seedColor)
-                    it.updateColors()
-                }
-            }
-
-            override fun onRegionDarknessChanged(isRegionDark: Boolean) {
-                layers.forEach { it.faceEvents.onRegionDarknessChanged(isRegionDark) }
-            }
-
-            override fun onReactiveAxesChanged(axes: List<ClockReactiveSetting>) {}
-
-            /**
-             * targetRegion passed to all customized clock applies counter translationY of
-             * KeyguardStatusView 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
-                // it needs to offset back the translation of keyguard_large_clock_top_margin
-                if (view is DigitalClockFaceView && view.isAlignedWithScreen()) {
-                    val topMargin = getKeyguardLargeClockTopMargin(assets)
-                    targetRegion?.let {
-                        val (_, yDiff) = computeLayoutDiff(view, it, isLargeClock)
-                        // In LS, we use yDiff to counter translate
-                        // the translation of KeyguardLargeClockTopMargin
-                        // With the targetRegion passed from picker,
-                        // we will have yDiff = 0, no translation is needed for weather clock
-                        if (yDiff.toInt() != 0) view.translationY = yDiff - topMargin / 2
-                    }
-                    return
-                }
-
-                var maxWidth = 0f
-                var maxHeight = 0f
-
-                for (layer in layers) {
-                    layer.faceEvents.onTargetRegionChanged(targetRegion)
-                    maxWidth = max(maxWidth, layer.view.layoutParams.width.toFloat())
-                    maxHeight = max(maxHeight, layer.view.layoutParams.height.toFloat())
-                }
-
-                val lp =
-                    if (maxHeight <= 0 || maxWidth <= 0 || targetRegion == null) {
-                        // No specified width/height. Just match parent size.
-                        FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
-                    } else {
-                        // Scale to fit in targetRegion based on largest child elements.
-                        val ratio = maxWidth / maxHeight
-                        val targetRatio = targetRegion.width() / targetRegion.height().toFloat()
-                        val scale =
-                            if (ratio > targetRatio) targetRegion.width() / maxWidth
-                            else targetRegion.height() / maxHeight
-
-                        FrameLayout.LayoutParams(
-                            (maxWidth * scale).toInt(),
-                            (maxHeight * scale).toInt(),
-                        )
-                    }
-
-                lp.gravity = Gravity.CENTER
-                view.layoutParams = lp
-                targetRegion?.let {
-                    val (xDiff, yDiff) = computeLayoutDiff(view, it, isLargeClock)
-                    view.translationX = xDiff
-                    view.translationY = yDiff
-                }
-            }
-
-            override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {}
-
-            override fun onWeatherDataChanged(data: WeatherData) {
-                layers.forEach { it.events.onWeatherDataChanged(data) }
-            }
-
-            override fun onAlarmDataChanged(data: AlarmData) {
-                layers.forEach { it.events.onAlarmDataChanged(data) }
-            }
-
-            override fun onZenDataChanged(data: ZenData) {
-                layers.forEach { it.events.onZenDataChanged(data) }
-            }
-        }
-
-    override val animations =
-        object : ClockAnimations {
-            override fun enter() {
-                layers.forEach { it.animations.enter() }
-            }
-
-            override fun doze(fraction: Float) {
-                layers.forEach { it.animations.doze(fraction) }
-            }
-
-            override fun fold(fraction: Float) {
-                layers.forEach { it.animations.fold(fraction) }
-            }
-
-            override fun charge() {
-                layers.forEach { it.animations.charge() }
-            }
-
-            override fun onPickerCarouselSwiping(swipingFraction: Float) {
-                face.pickerScale?.let {
-                    view.scaleX = swipingFraction * (1 - it.scaleX) + it.scaleX
-                    view.scaleY = swipingFraction * (1 - it.scaleY) + it.scaleY
-                }
-                if (!(view is DigitalClockFaceView && view.isAlignedWithScreen())) {
-                    val topMargin = getKeyguardLargeClockTopMargin(assets)
-                    view.translationY = topMargin / 2F * swipingFraction
-                }
-                layers.forEach { it.animations.onPickerCarouselSwiping(swipingFraction) }
-                view.invalidate()
-            }
-
-            override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {
-                layers.forEach { it.animations.onPositionUpdated(fromLeft, direction, fraction) }
-            }
-
-            override fun onPositionUpdated(distance: Float, fraction: Float) {
-                layers.forEach { it.animations.onPositionUpdated(distance, fraction) }
-            }
-        }
-
-    private fun getTickRate(): ClockTickRate {
-        var tickRate = ClockTickRate.PER_MINUTE
-        for (layer in layers) {
-            if (layer.config.tickRate.value < tickRate.value) {
-                tickRate = layer.config.tickRate
-            }
-        }
-        return tickRate
-    }
-
-    private fun getKeyguardLargeClockTopMargin(assets: AssetLoader): Int {
-        val topMarginRes =
-            assets.resolveResourceId(null, "dimen", "keyguard_large_clock_top_margin")
-        if (topMarginRes != null) {
-            val (res, id) = topMarginRes
-            return res.getDimensionPixelSize(id)
-        }
-        return 0
-    }
-}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt
index f71543e..5d1a2db 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt
@@ -16,25 +16,12 @@
 
 package com.android.systemui.shared.clocks
 
-import android.content.Context
 import android.view.View
 import androidx.annotation.VisibleForTesting
-import com.android.systemui.log.core.MessageBuffer
 import com.android.systemui.plugins.clocks.ClockAnimations
 import com.android.systemui.plugins.clocks.ClockEvents
 import com.android.systemui.plugins.clocks.ClockFaceConfig
 import com.android.systemui.plugins.clocks.ClockFaceEvents
-import com.android.systemui.shared.clocks.view.SimpleDigitalClockTextView
-import kotlin.reflect.KClass
-
-typealias LayerControllerConstructor =
-    (
-        ctx: Context,
-        assets: AssetLoader,
-        layer: ClockLayer,
-        isLargeClock: Boolean,
-        messageBuffer: MessageBuffer,
-    ) -> SimpleClockLayerController
 
 interface SimpleClockLayerController {
     val view: View
@@ -48,55 +35,4 @@
     // Called immediately after either onColorPaletteChanged or onSeedColorChanged is called.
     // Provided for convience to not duplicate color update logic after state updated.
     fun updateColors() {}
-
-    companion object Factory {
-        val constructorMap = mutableMapOf<Pair<KClass<*>, KClass<*>?>, LayerControllerConstructor>()
-
-        internal inline fun <reified TLayer> registerConstructor(
-            noinline constructor: LayerControllerConstructor,
-        ) where TLayer : ClockLayer {
-            constructorMap[Pair(TLayer::class, null)] = constructor
-        }
-
-        inline fun <reified TLayer, reified TStyle> registerTextConstructor(
-            noinline constructor: LayerControllerConstructor,
-        ) where TLayer : ClockLayer, TStyle : TextStyle {
-            constructorMap[Pair(TLayer::class, TStyle::class)] = constructor
-        }
-
-        init {
-            registerConstructor<ComposedDigitalHandLayer>(::ComposedDigitalLayerController)
-            registerTextConstructor<DigitalHandLayer, FontTextStyle>(::createSimpleDigitalLayer)
-        }
-
-        private fun createSimpleDigitalLayer(
-            ctx: Context,
-            assets: AssetLoader,
-            layer: ClockLayer,
-            isLargeClock: Boolean,
-            messageBuffer: MessageBuffer
-        ): SimpleClockLayerController {
-            val view = SimpleDigitalClockTextView(ctx, messageBuffer)
-            return SimpleDigitalHandLayerController(
-                ctx,
-                assets,
-                layer as DigitalHandLayer,
-                view,
-                messageBuffer
-            )
-        }
-
-        fun create(
-            ctx: Context,
-            assets: AssetLoader,
-            layer: ClockLayer,
-            isLargeClock: Boolean,
-            messageBuffer: MessageBuffer
-        ): SimpleClockLayerController {
-            val styleClass = if (layer is DigitalHandLayer) layer.style::class else null
-            val key = Pair(layer::class, styleClass)
-            return constructorMap[key]?.invoke(ctx, assets, layer, isLargeClock, messageBuffer)
-                ?: throw IllegalArgumentException("Unrecognized ClockLayer type: $key")
-        }
-    }
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
index a3240f8..ce1eae4 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
@@ -42,7 +42,8 @@
 
 open class SimpleDigitalHandLayerController<T>(
     private val ctx: Context,
-    private val assets: AssetLoader,
+    private val resources: Resources,
+    private val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources
     private val layer: DigitalHandLayer,
     override val view: T,
     messageBuffer: MessageBuffer,
@@ -68,7 +69,7 @@
         view.layoutParams =
             RelativeLayout.LayoutParams(
                 ViewGroup.LayoutParams.WRAP_CONTENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT
+                ViewGroup.LayoutParams.WRAP_CONTENT,
             )
         if (layer.alignment != null) {
             layer.alignment.verticalAlignment?.let { view.verticalAlignment = it }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt
index eb72346..81efcb9 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.shared.clocks.LogUtil
 import java.util.Locale
 
+// TODO(b/364680879): Merge w/ only subclass FlexClockView
 abstract class DigitalClockFaceView(ctx: Context, messageBuffer: MessageBuffer) : FrameLayout(ctx) {
     protected val logger = Logger(messageBuffer, this::class.simpleName!!)
         get() = field ?: LogUtil.FALLBACK_INIT_LOGGER
@@ -140,7 +141,6 @@
     open val useCustomClockScene
         get() = false
 
-    // TODO: implement ClockEventUnion?
     open fun onLocaleChanged(locale: Locale) {}
 
     open fun onWeatherDataChanged(data: WeatherData) {}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
index c29c8da..25b2ad7 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
@@ -35,7 +35,7 @@
 
 fun clamp(value: Float, minVal: Float, maxVal: Float): Float = max(min(value, maxVal), minVal)
 
-class FlexClockView(context: Context, val assetLoader: AssetLoader, messageBuffer: MessageBuffer) :
+class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: MessageBuffer) :
     DigitalClockFaceView(context, messageBuffer) {
     override var digitalClockTextViewMap = mutableMapOf<Int, SimpleDigitalClockTextView>()
     val digitLeftTopMap = mutableMapOf<Int, Point>()
@@ -57,11 +57,9 @@
     private var prevY = 0f
     private var isDown = false
 
-    // TODO(b/340253296): Genericize; json spec
     private var wght = 603f
     private var wdth = 100f
 
-    // TODO(b/340253296): Json spec
     private val MAX_WGHT = 950f
     private val MIN_WGHT = 50f
     private val WGHT_SCALE = 0.5f
@@ -71,7 +69,6 @@
     private val WDTH_SCALE = 0.2f
 
     override fun onTouchEvent(evt: MotionEvent): Boolean {
-        // TODO(b/340253296): implement on DigitalClockFaceView?
         if (!isReactiveTouchInteractionEnabled) {
             return super.onTouchEvent(evt)
         }
@@ -94,12 +91,11 @@
                 prevX = evt.x
                 prevY = evt.y
 
-                // TODO(b/340253296): Genericize; json spec
                 val fvar = "'wght' $wght, 'wdth' $wdth, 'opsz' 144, 'ROND' 100"
                 digitalClockTextViewMap.forEach { (_, view) ->
                     val textStyle = view.textStyle as FontTextStyle
                     textStyle.fontVariation = fvar
-                    view.applyStyles(assetLoader, textStyle, view.aodStyle)
+                    view.applyStyles(assets, textStyle, view.aodStyle)
                 }
 
                 requestLayout()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index 2bb9e68..00c5577 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -156,8 +156,12 @@
         when(mResources.getInteger(R.integer.keyguard_date_weather_view_invisibility))
                 .thenReturn(INVISIBLE);
 
-        when(mView.findViewById(R.id.lockscreen_clock_view_large)).thenReturn(mLargeClockFrame);
-        when(mView.findViewById(R.id.lockscreen_clock_view)).thenReturn(mSmallClockFrame);
+        when(mView
+                .findViewById(com.android.systemui.customization.R.id.lockscreen_clock_view_large))
+                .thenReturn(mLargeClockFrame);
+        when(mView
+                .findViewById(com.android.systemui.customization.R.id.lockscreen_clock_view))
+                .thenReturn(mSmallClockFrame);
         when(mSmallClockView.getContext()).thenReturn(getContext());
         when(mLargeClockView.getContext()).thenReturn(getContext());
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 0bf9d12..4ed5fd0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -113,8 +113,10 @@
         });
         mKeyguardClockSwitch =
                 (KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null);
-        mSmallClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view);
-        mLargeClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view_large);
+        mSmallClockFrame = mKeyguardClockSwitch
+                .findViewById(com.android.systemui.customization.R.id.lockscreen_clock_view);
+        mLargeClockFrame = mKeyguardClockSwitch
+                .findViewById(com.android.systemui.customization.R.id.lockscreen_clock_view_large);
         mStatusArea = mKeyguardClockSwitch.findViewById(R.id.keyguard_status_area);
         mKeyguardClockSwitch.mChildrenAreLaidOut = true;
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
index 2e41246..245388c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
@@ -17,10 +17,10 @@
 package com.android.keyguard
 
 import android.view.View
-import android.view.ViewGroup
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.customization.R as customR
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -88,7 +88,7 @@
         underTest.statusViewCentered = true
 
         val view = View(context)
-        whenever(keyguardRootView.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(
+        whenever(keyguardRootView.findViewById<View>(customR.id.lockscreen_clock_view_large)).thenReturn(
             view
         )
 
@@ -110,7 +110,7 @@
         whenever(statusBarStateController.getState()).thenReturn(SHADE)
 
         val view = View(context)
-        whenever(keyguardRootView.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(
+        whenever(keyguardRootView.findViewById<View>(customR.id.lockscreen_clock_view_large)).thenReturn(
             view
         )
 
@@ -134,7 +134,7 @@
         val view = View(context)
         whenever(
             notificationShadeWindowView
-                .findViewById<View>(R.id.lockscreen_clock_view_large)
+                .findViewById<View>(customR.id.lockscreen_clock_view_large)
         ).thenReturn(view)
 
         progressListener.onTransitionStarted()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 6c6de61..cd8b2e1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -440,6 +440,28 @@
         }
 
     @Test
+    fun displayAdditionEvent_emptyByDefault() =
+        testScope.runTest {
+            setDisplays(1, 2, 3)
+
+            val lastAddedDisplay by lastDisplayAdditionEvent()
+
+            assertThat(lastAddedDisplay).isNull()
+        }
+
+    @Test
+    fun displayAdditionEvent_displaysAdded_doesNotReplayEventsToNewSubscribers() =
+        testScope.runTest {
+            val priorDisplayAdded by lastDisplayAdditionEvent()
+            setDisplays(1)
+            sendOnDisplayAdded(1)
+            assertThat(priorDisplayAdded?.displayId).isEqualTo(1)
+
+            val lastAddedDisplay by collectLastValue(displayRepository.displayAdditionEvent)
+            assertThat(lastAddedDisplay).isNull()
+        }
+
+    @Test
     fun defaultDisplayOff_changes() =
         testScope.runTest {
             val defaultDisplayOff by latestDefaultDisplayOffFlowValue()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
index 1c99eff..d94c97a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
@@ -28,12 +28,12 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.customization.R as customR
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
-import com.android.systemui.res.R
 import com.android.systemui.shared.R as sharedR
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
 import com.android.systemui.util.mockito.any
@@ -135,7 +135,7 @@
         assertThat(smartspaceConstraints.layout.topToBottom).isEqualTo(dateView.id)
 
         val dateConstraints = constraintSet.getConstraint(dateView.id)
-        assertThat(dateConstraints.layout.topToBottom).isEqualTo(R.id.lockscreen_clock_view)
+        assertThat(dateConstraints.layout.topToBottom).isEqualTo(customR.id.lockscreen_clock_view)
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index 3f550ca..0d5ddae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -18,9 +18,12 @@
 
 import static com.android.systemui.screenrecord.RecordingService.GROUP_KEY_ERROR_SAVING;
 import static com.android.systemui.screenrecord.RecordingService.GROUP_KEY_SAVED;
+import static com.android.systemui.screenrecord.RecordingService.NOTIF_GROUP_ID_ERROR_SAVING;
+import static com.android.systemui.screenrecord.RecordingService.NOTIF_GROUP_ID_SAVED;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -235,7 +238,9 @@
 
         // Processing notification
         ArgumentCaptor<Notification> notifCaptor = ArgumentCaptor.forClass(Notification.class);
-        verify(mNotificationManager).notifyAsUser(any(), anyInt(), notifCaptor.capture(), any());
+        ArgumentCaptor<Integer> notifIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mNotificationManager)
+                .notifyAsUser(any(), notifIdCaptor.capture(), notifCaptor.capture(), any());
         assertEquals(GROUP_KEY_SAVED, notifCaptor.getValue().getGroup());
 
         reset(mNotificationManager);
@@ -243,7 +248,7 @@
         mRunnableCaptor.getValue().run();
 
         verify(mNotificationManager, times(2))
-                .notifyAsUser(any(), anyInt(), notifCaptor.capture(), any());
+                .notifyAsUser(any(), notifIdCaptor.capture(), notifCaptor.capture(), any());
         // Saved notification
         Notification saveNotification = notifCaptor.getAllValues().get(0);
         assertFalse(saveNotification.isGroupSummary());
@@ -252,6 +257,10 @@
         Notification groupSummaryNotification = notifCaptor.getAllValues().get(1);
         assertTrue(groupSummaryNotification.isGroupSummary());
         assertEquals(GROUP_KEY_SAVED, groupSummaryNotification.getGroup());
+
+        // Verify the group notification ID and the individual notification ID are different
+        assertNotEquals(NOTIF_GROUP_ID_SAVED, (int) notifIdCaptor.getAllValues().get(0));
+        assertEquals(NOTIF_GROUP_ID_SAVED, (int) notifIdCaptor.getAllValues().get(1));
     }
 
     @Test
@@ -264,9 +273,12 @@
 
         verify(mRecordingService).createErrorSavingNotification(any());
         ArgumentCaptor<Notification> notifCaptor = ArgumentCaptor.forClass(Notification.class);
-        verify(mNotificationManager).notifyAsUser(any(), anyInt(), notifCaptor.capture(), any());
+        ArgumentCaptor<Integer> notifIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mNotificationManager)
+                .notifyAsUser(any(), notifIdCaptor.capture(), notifCaptor.capture(), any());
         assertTrue(notifCaptor.getValue().isGroupSummary());
         assertEquals(GROUP_KEY_ERROR_SAVING, notifCaptor.getValue().getGroup());
+        assertEquals(NOTIF_GROUP_ID_ERROR_SAVING, (int) notifIdCaptor.getValue());
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 905301e..943fb62 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -43,6 +43,7 @@
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -74,6 +75,7 @@
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
     @Mock private lateinit var statusBarWindowController: StatusBarWindowController
+    @Mock private lateinit var statusBarWindowControllerStore: StatusBarWindowControllerStore
     @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
     @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
     @Mock private lateinit var windowManager: WindowManager
@@ -105,6 +107,8 @@
         MockitoAnnotations.initMocks(this)
         whenever(windowManager.defaultDisplay).thenReturn(display)
         whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
+        whenever(statusBarWindowControllerStore.defaultDisplay)
+            .thenReturn(statusBarWindowController)
         shadeController =
             ShadeControllerImpl(
                 commandQueue,
@@ -113,7 +117,7 @@
                 keyguardStateController,
                 statusBarStateController,
                 statusBarKeyguardViewManager,
-                statusBarWindowController,
+                statusBarWindowControllerStore,
                 deviceProvisionedController,
                 notificationShadeWindowController,
                 0,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
index 9142972..f64387c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.fragments.FragmentHostManager
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
 import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
 import org.junit.Assert.assertThrows
@@ -39,7 +40,8 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class StatusBarInitializerTest : SysuiTestCase() {
-    val windowController = mock(StatusBarWindowController::class.java)
+    private val windowController = mock(StatusBarWindowController::class.java)
+    private val windowControllerStore = mock(StatusBarWindowControllerStore::class.java)
 
     @Before
     fun setup() {
@@ -52,15 +54,16 @@
         whenever(fragmentHostManager.fragmentManager).thenReturn(fragmentManager)
         whenever(fragmentManager.beginTransaction()).thenReturn(transaction)
         whenever(transaction.replace(any(), any(), any())).thenReturn(transaction)
-
+        whenever(windowControllerStore.defaultDisplay).thenReturn(windowController)
         whenever(windowController.fragmentHostManager).thenReturn(fragmentHostManager)
     }
 
     val underTest =
         StatusBarInitializerImpl(
-            windowController,
-            { mock(CollapsedStatusBarFragment::class.java) },
-            setOf(),
+            displayId = context.displayId,
+            statusBarWindowControllerStore = windowControllerStore,
+            collapsedStatusBarFragmentProvider = { mock(CollapsedStatusBarFragment::class.java) },
+            creationListeners = setOf(),
         )
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt
index 5803365..bb3fb1e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt
@@ -44,7 +44,7 @@
 import com.android.systemui.statusbar.window.data.model.StatusBarWindowState
 import com.android.systemui.statusbar.window.data.repository.fakeStatusBarWindowStateRepositoryStore
 import com.android.systemui.statusbar.window.data.repository.statusBarWindowStateRepositoryStore
-import com.android.systemui.statusbar.window.fakeStatusBarWindowController
+import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore
 import com.android.systemui.testKosmos
 import com.android.wm.shell.bubbles.bubbles
 import com.google.common.truth.Truth.assertThat
@@ -67,7 +67,7 @@
         }
     private val testScope = kosmos.testScope
     private val statusBarViewController = kosmos.mockPhoneStatusBarViewController
-    private val statusBarWindowController = kosmos.fakeStatusBarWindowController
+    private val statusBarWindowControllerStore = kosmos.fakeStatusBarWindowControllerStore
     private val statusBarModeRepository = kosmos.fakeStatusBarModeRepository
     private val pluginDependencyProvider = kosmos.mockPluginDependencyProvider
     private val notificationShadeWindowViewController =
@@ -94,7 +94,7 @@
     fun start_attachesWindow() {
         orchestrator.start()
 
-        assertThat(statusBarWindowController.isAttached).isTrue()
+        assertThat(statusBarWindowControllerStore.defaultDisplay.isAttached).isTrue()
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
index 984bda1..a629b24 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
@@ -53,6 +54,7 @@
 
     @get:Rule val animatorTestRule = AnimatorTestRule(this)
     @Mock private lateinit var sbWindowController: StatusBarWindowController
+    @Mock private lateinit var sbWindowControllerStore: StatusBarWindowControllerStore
     @Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider
 
     private var testView = TestView(mContext)
@@ -61,7 +63,7 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-
+        whenever(sbWindowControllerStore.defaultDisplay).thenReturn(sbWindowController)
         // StatusBarWindowController is mocked. The addViewToWindow function needs to be mocked to
         // ensure that the chip view is added to a parent view
         whenever(sbWindowController.addViewToWindow(any(), any())).then {
@@ -93,8 +95,8 @@
         controller =
             SystemEventChipAnimationController(
                 context = mContext,
-                statusBarWindowController = sbWindowController,
-                contentInsetsProvider = insetsProvider
+                statusBarWindowControllerStore = sbWindowControllerStore,
+                contentInsetsProvider = insetsProvider,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
index 1797995..bac79a9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
@@ -56,6 +56,7 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -94,6 +95,7 @@
     @Mock private lateinit var activityTransitionAnimator: ActivityTransitionAnimator
     @Mock private lateinit var lockScreenUserManager: NotificationLockscreenUserManager
     @Mock private lateinit var statusBarWindowController: StatusBarWindowController
+    @Mock private lateinit var statusBarWindowControllerStore: StatusBarWindowControllerStore
     @Mock private lateinit var notifShadeWindowController: NotificationShadeWindowController
     @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
     @Mock private lateinit var keyguardStateController: KeyguardStateController
@@ -112,6 +114,7 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        `when`(statusBarWindowControllerStore.defaultDisplay).thenReturn(statusBarWindowController)
         underTest =
             LegacyActivityStarterInternalImpl(
                 centralSurfacesOptLazy = { Optional.of(centralSurfaces) },
@@ -128,7 +131,7 @@
                 context = context,
                 displayId = DISPLAY_ID,
                 lockScreenUserManager = lockScreenUserManager,
-                statusBarWindowController = statusBarWindowController,
+                statusBarWindowControllerStore = statusBarWindowControllerStore,
                 wakefulnessLifecycle = wakefulnessLifecycle,
                 keyguardStateController = keyguardStateController,
                 statusBarStateController = statusBarStateController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
index 597e2e4..e0d9fac 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
@@ -50,6 +50,7 @@
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.time.FakeSystemClock
@@ -74,6 +75,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
 
 private const val CALL_UID = 900
 
@@ -106,6 +108,7 @@
     @Mock private lateinit var mockActivityStarter: ActivityStarter
     @Mock private lateinit var mockIActivityManager: IActivityManager
     @Mock private lateinit var mockStatusBarWindowController: StatusBarWindowController
+    @Mock private lateinit var mockStatusBarWindowControllerStore: StatusBarWindowControllerStore
 
     private lateinit var chipView: View
 
@@ -118,6 +121,8 @@
 
         MockitoAnnotations.initMocks(this)
         val notificationCollection = mock(CommonNotifCollection::class.java)
+        whenever(mockStatusBarWindowControllerStore.defaultDisplay)
+            .thenReturn(mockStatusBarWindowController)
 
         controller =
             OngoingCallController(
@@ -131,7 +136,7 @@
                 mainExecutor,
                 mockIActivityManager,
                 DumpManager(),
-                mockStatusBarWindowController,
+                mockStatusBarWindowControllerStore,
                 mockSwipeStatusBarAwayGestureHandler,
                 statusBarModeRepository,
                 logcatLogBuffer("OngoingCallControllerViaListenerTest"),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
index dfe01bf..2ad50cc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
@@ -52,6 +52,7 @@
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -93,6 +94,7 @@
     private val mockActivityStarter = kosmos.activityStarter
     private val mockIActivityManager = mock<IActivityManager>()
     private val mockStatusBarWindowController = mock<StatusBarWindowController>()
+    private val mockStatusBarWindowControllerStore = mock<StatusBarWindowControllerStore>()
 
     private lateinit var chipView: View
 
@@ -103,6 +105,8 @@
             chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null)
         }
 
+        whenever(mockStatusBarWindowControllerStore.defaultDisplay)
+            .thenReturn(mockStatusBarWindowController)
         controller =
             OngoingCallController(
                 testScope.backgroundScope,
@@ -115,7 +119,7 @@
                 mainExecutor,
                 mockIActivityManager,
                 DumpManager(),
-                mockStatusBarWindowController,
+                mockStatusBarWindowControllerStore,
                 mockSwipeStatusBarAwayGestureHandler,
                 statusBarModeRepository,
                 logcatLogBuffer("OngoingCallControllerViaRepoTest"),
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index fc9c917..8bef475 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -26,7 +26,7 @@
     android:clipChildren="false"
     android:layout_gravity="center_horizontal|top">
     <com.android.keyguard.KeyguardClockFrame
-        android:id="@+id/lockscreen_clock_view"
+        android:id="@id/lockscreen_clock_view"
         android:layout_width="wrap_content"
         android:layout_height="@dimen/small_clock_height"
         android:layout_alignParentStart="true"
@@ -35,7 +35,7 @@
         android:paddingStart="@dimen/clock_padding_start"
         android:visibility="invisible" />
     <com.android.keyguard.KeyguardClockFrame
-        android:id="@+id/lockscreen_clock_view_large"
+        android:id="@id/lockscreen_clock_view_large"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:clipChildren="false"
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 4a96e9e..c7ea980 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -193,12 +193,16 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         if (!MigrateClocksToBlueprint.isEnabled()) {
-            mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
-            mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
+            mSmallClockFrame = findViewById(
+                    com.android.systemui.customization.R.id.lockscreen_clock_view);
+            mLargeClockFrame = findViewById(
+                    com.android.systemui.customization.R.id.lockscreen_clock_view_large);
             mStatusArea = findViewById(R.id.keyguard_status_area);
         } else {
-            removeView(findViewById(R.id.lockscreen_clock_view));
-            removeView(findViewById(R.id.lockscreen_clock_view_large));
+            removeView(findViewById(
+                    com.android.systemui.customization.R.id.lockscreen_clock_view));
+            removeView(findViewById(
+                    com.android.systemui.customization.R.id.lockscreen_clock_view_large));
         }
         onConfigChanged();
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index d468f2f..7cba845 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -241,8 +241,10 @@
         mKeyguardSliceViewController.init();
 
         if (!MigrateClocksToBlueprint.isEnabled()) {
-            mSmallClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
-            mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
+            mSmallClockFrame = mView
+                .findViewById(com.android.systemui.customization.R.id.lockscreen_clock_view);
+            mLargeClockFrame = mView
+                .findViewById(com.android.systemui.customization.R.id.lockscreen_clock_view_large);
         }
 
         if (!mOnlyClock) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 63a4af9..0684824 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -534,7 +534,8 @@
         // KeyguardClockViewBinder
         if (customClockAnimation && !MigrateClocksToBlueprint.isEnabled()) {
             // Find the clock, so we can exclude it from this transition.
-            FrameLayout clockContainerView = mView.findViewById(R.id.lockscreen_clock_view_large);
+            FrameLayout clockContainerView = mView.findViewById(
+                    com.android.systemui.customization.R.id.lockscreen_clock_view_large);
 
             // The clock container can sometimes be null. If it is, just fall back to the
             // old animation rather than setting up the custom animations.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index 19d918f..5a02486 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.view.View
+import com.android.systemui.customization.R as customR
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -98,12 +99,12 @@
             viewsIdToTranslate =
                 setOf(
                     ViewIdToTranslate(
-                        viewId = R.id.lockscreen_clock_view_large,
+                        viewId = customR.id.lockscreen_clock_view_large,
                         direction = START,
                         shouldBeAnimated = filterKeyguardAndSplitShadeOnly
                     ),
                     ViewIdToTranslate(
-                        viewId = R.id.lockscreen_clock_view,
+                        viewId = customR.id.lockscreen_clock_view,
                         direction = START,
                         shouldBeAnimated = filterKeyguard
                     ),
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index a301155..40c1f0f9 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -52,7 +52,7 @@
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.window.StatusBarWindowController;
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
 import com.android.systemui.tuner.TunerService;
 
 import dagger.Lazy;
@@ -148,7 +148,7 @@
     @Inject Lazy<SystemUIDialogManager> mSystemUIDialogManagerLazy;
     @Inject Lazy<DialogTransitionAnimator> mDialogTransitionAnimatorLazy;
     @Inject Lazy<UserTracker> mUserTrackerLazy;
-    @Inject Lazy<StatusBarWindowController> mStatusBarWindowControllerLazy;
+    @Inject Lazy<StatusBarWindowControllerStore> mStatusBarWindowControllerStoreLazy;
 
     @Inject
     public Dependency() {
@@ -192,7 +192,8 @@
         mProviders.put(SystemUIDialogManager.class, mSystemUIDialogManagerLazy::get);
         mProviders.put(DialogTransitionAnimator.class, mDialogTransitionAnimatorLazy::get);
         mProviders.put(UserTracker.class, mUserTrackerLazy::get);
-        mProviders.put(StatusBarWindowController.class, mStatusBarWindowControllerLazy::get);
+        mProviders.put(
+                StatusBarWindowControllerStore.class, mStatusBarWindowControllerStoreLazy::get);
 
         Dependency.setInstance(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Default.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Default.java
new file mode 100644
index 0000000..1950d6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Default.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dagger.qualifiers;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface Default {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 6a69136..034cb31 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.display.data.DisplayEvent
 import com.android.systemui.util.Compile
+import com.android.systemui.util.kotlin.pairwiseBy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -41,11 +42,12 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterIsInstance
-import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
@@ -146,11 +148,6 @@
     override val displayChangeEvent: Flow<Int> =
         allDisplayEvents.filterIsInstance<DisplayEvent.Changed>().map { event -> event.displayId }
 
-    override val displayAdditionEvent: Flow<Display?> =
-        allDisplayEvents.filterIsInstance<DisplayEvent.Added>().map {
-            getDisplayFromDisplayManager(it.displayId)
-        }
-
     override val displayRemovalEvent: Flow<Int> =
         allDisplayEvents.filterIsInstance<DisplayEvent.Removed>().map { it.displayId }
 
@@ -212,6 +209,17 @@
      */
     override val displays: StateFlow<Set<Display>> = enabledDisplays
 
+    /**
+     * Implementation that maps from [displays], instead of [allDisplayEvents] for 2 reasons:
+     * 1. Guarantee that it emits __after__ [displays] emitted. This way it is guaranteed that
+     *    calling [getDisplay] for the newly added display will be non-null.
+     * 2. Reuse the existing instance of [Display] without a new call to [DisplayManager].
+     */
+    override val displayAdditionEvent: Flow<Display?> =
+        displays
+            .pairwiseBy { previousDisplays, currentDisplays -> currentDisplays - previousDisplays }
+            .flatMapLatest { it.asFlow() }
+
     val _ignoredDisplayIds = MutableStateFlow<Set<Int>>(emptySet())
     private val ignoredDisplayIds: Flow<Set<Int>> = _ignoredDisplayIds.debugLog("ignoredDisplayIds")
 
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index aa1873c..162047b 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -135,6 +135,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.EmergencyDialerConstants;
@@ -248,7 +249,7 @@
     private final IStatusBarService mStatusBarService;
     protected final LightBarController mLightBarController;
     protected final NotificationShadeWindowController mNotificationShadeWindowController;
-    private final StatusBarWindowController mStatusBarWindowController;
+    private final StatusBarWindowControllerStore mStatusBarWindowControllerStore;
     private final IWindowManager mIWindowManager;
     private final Executor mBackgroundExecutor;
     private final RingerModeTracker mRingerModeTracker;
@@ -364,7 +365,7 @@
             IStatusBarService statusBarService,
             LightBarController lightBarController,
             NotificationShadeWindowController notificationShadeWindowController,
-            StatusBarWindowController statusBarWindowController,
+            StatusBarWindowControllerStore statusBarWindowControllerStore,
             IWindowManager iWindowManager,
             @Background Executor backgroundExecutor,
             UiEventLogger uiEventLogger,
@@ -400,7 +401,7 @@
         mStatusBarService = statusBarService;
         mLightBarController = lightBarController;
         mNotificationShadeWindowController = notificationShadeWindowController;
-        mStatusBarWindowController = statusBarWindowController;
+        mStatusBarWindowControllerStore = statusBarWindowControllerStore;
         mIWindowManager = iWindowManager;
         mBackgroundExecutor = backgroundExecutor;
         mRingerModeTracker = ringerModeTracker;
@@ -708,7 +709,7 @@
                 mLightBarController,
                 mKeyguardStateController,
                 mNotificationShadeWindowController,
-                mStatusBarWindowController,
+                mStatusBarWindowControllerStore.getDefaultDisplay(),
                 this::onRefresh,
                 mKeyguardShowing,
                 mPowerAdapter,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 063adc8..3230285 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -30,8 +30,6 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.keyguard.KeyguardStatusView
 import com.android.keyguard.KeyguardStatusViewController
-import com.android.keyguard.LegacyLockIconViewController
-import com.android.keyguard.LockIconView
 import com.android.keyguard.dagger.KeyguardStatusViewComponent
 import com.android.systemui.CoreStartable
 import com.android.systemui.biometrics.ui.binder.DeviceEntryUnlockTrackerViewBinder
@@ -39,7 +37,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
 import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
@@ -94,7 +91,6 @@
     private val configuration: ConfigurationState,
     private val context: Context,
     private val keyguardIndicationController: KeyguardIndicationController,
-    private val lockIconViewController: Lazy<LegacyLockIconViewController>,
     private val shadeInteractor: ShadeInteractor,
     private val interactionJankMonitor: InteractionJankMonitor,
     private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
@@ -171,10 +167,6 @@
     private fun initializeViews() {
         val indicationArea = KeyguardIndicationArea(context, null)
         keyguardIndicationController.setIndicationArea(indicationArea)
-
-        if (!DeviceEntryUdfpsRefactor.isEnabled) {
-            lockIconViewController.get().setLockIconView(LockIconView(context, null))
-        }
     }
 
     private fun bindKeyguardRootView() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d28b08f..fbc76c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -139,7 +139,6 @@
 import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.ui.viewmodel.DreamViewModel;
 import com.android.systemui.dump.DumpManager;
@@ -3569,9 +3568,7 @@
         }
 
         // Ensure that keyguard becomes visible if the going away animation is canceled
-        if (showKeyguard && !KeyguardWmStateRefactor.isEnabled()
-                && (MigrateClocksToBlueprint.isEnabled()
-                    || DeviceEntryUdfpsRefactor.isEnabled())) {
+        if (showKeyguard && !KeyguardWmStateRefactor.isEnabled()) {
             mKeyguardInteractor.showKeyguard();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
index fb97191..7ca2c20 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
@@ -22,7 +22,6 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.tracing.coroutines.launch
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -34,13 +33,7 @@
 
     /** Updates UI for the UDFPS icon on the alternate bouncer. */
     @JvmStatic
-    fun bind(
-        view: DeviceEntryIconView,
-        viewModel: AlternateBouncerUdfpsIconViewModel,
-    ) {
-        if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) {
-            return
-        }
+    fun bind(view: DeviceEntryIconView, viewModel: AlternateBouncerUdfpsIconViewModel) {
         val fgIconView = view.iconView
         val bgView = view.bgView
 
@@ -66,7 +59,7 @@
                 viewModel.fgViewModel.collect { fgViewModel ->
                     fgIconView.setImageState(
                         view.getIconState(fgViewModel.type, fgViewModel.useAodVariant),
-                        /* merge */ false
+                        /* merge */ false,
                     )
                     fgIconView.imageTintList = ColorStateList.valueOf(fgViewModel.tint)
                     fgIconView.setPadding(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index 7696273..1891af2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -33,7 +33,6 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder
 import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay
 import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel
@@ -95,7 +94,7 @@
     private var alternateBouncerView: ConstraintLayout? = null
 
     override fun start() {
-        if (!DeviceEntryUdfpsRefactor.isEnabled || SceneContainerFlag.isEnabled) {
+        if (SceneContainerFlag.isEnabled) {
             return
         }
 
@@ -182,14 +181,7 @@
     }
 
     /** Binds the view to the view-model, continuing to update the former based on the latter. */
-    fun bind(
-        view: ConstraintLayout,
-        alternateBouncerDependencies: AlternateBouncerDependencies,
-    ) {
-        if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) {
-            return
-        }
-
+    fun bind(view: ConstraintLayout, alternateBouncerDependencies: AlternateBouncerDependencies) {
         optionallyAddUdfpsViews(
             view = view,
             logger = alternateBouncerDependencies.logger,
@@ -287,10 +279,7 @@
                                         )
                                 }
                             view.addView(udfpsView)
-                            AlternateBouncerUdfpsViewBinder.bind(
-                                udfpsView,
-                                udfpsIconViewModel,
-                            )
+                            AlternateBouncerUdfpsViewBinder.bind(udfpsView, udfpsIconViewModel)
                         }
 
                         val constraintSet = ConstraintSet().apply { clone(view) }
@@ -310,17 +299,17 @@
                                 ConstraintSet.START,
                                 ConstraintSet.PARENT_ID,
                                 ConstraintSet.START,
-                                iconLocation.left
+                                iconLocation.left,
                             )
 
                             // udfpsA11yOverlayView:
                             constrainWidth(
                                 udfpsA11yOverlayViewId,
-                                ViewGroup.LayoutParams.MATCH_PARENT
+                                ViewGroup.LayoutParams.MATCH_PARENT,
                             )
                             constrainHeight(
                                 udfpsA11yOverlayViewId,
-                                ViewGroup.LayoutParams.MATCH_PARENT
+                                ViewGroup.LayoutParams.MATCH_PARENT,
                             )
                         }
                         constraintSet.applyTo(view)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index b951b73..a3f3342 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -30,7 +30,6 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.tracing.coroutines.launch
 import com.android.systemui.common.ui.view.LongPressHandlingView
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
@@ -68,7 +67,6 @@
         vibratorHelper: VibratorHelper,
         overrideColor: Color? = null,
     ): DisposableHandle {
-        DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()
         val disposables = DisposableHandles()
         val longPressHandlingView = view.longPressHandlingView
         val fgIconView = view.iconView
@@ -79,7 +77,7 @@
                     view: View,
                     x: Int,
                     y: Int,
-                    isA11yAction: Boolean
+                    isA11yAction: Boolean,
                 ) {
                     if (
                         !isA11yAction && falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)
@@ -87,14 +85,11 @@
                         Log.d(
                             TAG,
                             "Long press rejected because it is not a11yAction " +
-                                "and it is a falseLongTap"
+                                "and it is a falseLongTap",
                         )
                         return
                     }
-                    vibratorHelper.performHapticFeedback(
-                        view,
-                        HapticFeedbackConstants.CONFIRM,
-                    )
+                    vibratorHelper.performHapticFeedback(view, HapticFeedbackConstants.CONFIRM)
                     applicationScope.launch {
                         view.clearFocus()
                         view.clearAccessibilityFocus()
@@ -192,7 +187,7 @@
                         fgViewModel.viewModel.collect { viewModel ->
                             fgIconView.setImageState(
                                 view.getIconState(viewModel.type, viewModel.useAodVariant),
-                                /* merge */ false
+                                /* merge */ false,
                             )
                             if (viewModel.type.contentDescriptionResId != -1) {
                                 fgIconView.contentDescription =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index 00aa44f..0470e08 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -23,6 +23,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.tracing.coroutines.launch
+import com.android.systemui.customization.R as customR
 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.BaseBlueprintTransition
@@ -32,7 +33,6 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.res.R
 import com.android.systemui.shared.R as sharedR
 import com.android.systemui.util.kotlin.pairwise
 
@@ -128,7 +128,7 @@
     ) {
         val currentClock = viewModel.currentClock.value
         if (!DEBUG || currentClock == null) return
-        val smallClockViewId = R.id.lockscreen_clock_view
+        val smallClockViewId = customR.id.lockscreen_clock_view
         val largeClockViewId = currentClock.largeClock.layout.views[0].id
         val smartspaceDateId = sharedR.id.date_smartspace_view
         Log.i(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index 57cb10f..2d22556 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -35,7 +35,7 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.tracing.coroutines.launch
 import com.android.internal.policy.SystemBarUtils
-import com.android.systemui.customization.R as customizationR
+import com.android.systemui.customization.R as customR
 import com.android.systemui.keyguard.shared.model.ClockSizeSetting
 import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer
 import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection.Companion.getDimen
@@ -50,6 +50,8 @@
 
 /** Binder for the small clock view, large clock view. */
 object KeyguardPreviewClockViewBinder {
+    val lockId = View.generateViewId()
+
     @JvmStatic
     fun bind(
         largeClockHostView: View,
@@ -120,36 +122,36 @@
 
     private fun applyClockDefaultConstraints(context: Context, constraints: ConstraintSet) {
         constraints.apply {
-            constrainWidth(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
+            constrainWidth(customR.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
             // The following two lines make lockscreen_clock_view_large is constrained to available
             // height when it goes beyond constraints; otherwise, it use WRAP_CONTENT
-            constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
-            constrainMaxHeight(R.id.lockscreen_clock_view_large, 0)
+            constrainHeight(customR.id.lockscreen_clock_view_large, WRAP_CONTENT)
+            constrainMaxHeight(customR.id.lockscreen_clock_view_large, 0)
             val largeClockTopMargin =
                 SystemBarUtils.getStatusBarHeight(context) +
                     context.resources.getDimensionPixelSize(
-                        customizationR.dimen.small_clock_padding_top
+                        customR.dimen.small_clock_padding_top
                     ) +
                     context.resources.getDimensionPixelSize(
                         R.dimen.keyguard_smartspace_top_offset
                     ) +
                     getDimen(context, DATE_WEATHER_VIEW_HEIGHT) +
                     getDimen(context, ENHANCED_SMARTSPACE_HEIGHT)
-            connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
-            connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START)
+            connect(customR.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
+            connect(customR.id.lockscreen_clock_view_large, START, PARENT_ID, START)
             connect(
-                R.id.lockscreen_clock_view_large,
+                customR.id.lockscreen_clock_view_large,
                 ConstraintSet.END,
                 PARENT_ID,
                 ConstraintSet.END,
             )
 
-            // In preview, we'll show UDFPS icon for UDFPS devices
-            // and nothing for non-UDFPS devices,
-            // but we need position of device entry icon to constrain clock
-            if (getConstraint(R.id.lock_icon_view) != null) {
-                connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
-            } else {
+
+            // In preview, we'll show UDFPS icon for UDFPS devices and nothing for non-UDFPS
+            // devices, but we need position of device entry icon to constrain clock
+            if (getConstraint(lockId) != null) {
+                connect(customR.id.lockscreen_clock_view_large, BOTTOM, lockId, TOP)
+           } else {
                 // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection
                 val bottomPaddingPx =
                     context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
@@ -159,7 +161,7 @@
                 val lockIconRadiusPx = (defaultDensity * 36).toInt()
                 val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx
                 connect(
-                    R.id.lockscreen_clock_view_large,
+                    customR.id.lockscreen_clock_view_large,
                     BOTTOM,
                     PARENT_ID,
                     BOTTOM,
@@ -167,23 +169,23 @@
                 )
             }
 
-            constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT)
+            constrainWidth(customR.id.lockscreen_clock_view, WRAP_CONTENT)
             constrainHeight(
-                R.id.lockscreen_clock_view,
-                context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height),
+                customR.id.lockscreen_clock_view,
+                context.resources.getDimensionPixelSize(customR.dimen.small_clock_height),
             )
             connect(
-                R.id.lockscreen_clock_view,
+                customR.id.lockscreen_clock_view,
                 START,
                 PARENT_ID,
                 START,
-                context.resources.getDimensionPixelSize(customizationR.dimen.clock_padding_start) +
+                context.resources.getDimensionPixelSize(customR.dimen.clock_padding_start) +
                     context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal),
             )
             val smallClockTopMargin =
                 context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
                     Utils.getStatusBarHeaderHeightKeyguard(context)
-            connect(R.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin)
+            connect(customR.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index ee2ee52..ea70fd0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -52,8 +52,8 @@
 import com.android.systemui.common.ui.view.onApplyWindowInsets
 import com.android.systemui.common.ui.view.onLayoutChanged
 import com.android.systemui.common.ui.view.onTouchListener
+import com.android.systemui.customization.R as customR
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
 import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
@@ -180,16 +180,12 @@
                         }
                     }
 
-                    if (
-                        KeyguardBottomAreaRefactor.isEnabled || DeviceEntryUdfpsRefactor.isEnabled
-                    ) {
-                        launch("$TAG#alpha") {
-                            viewModel.alpha(viewState).collect { alpha ->
-                                view.alpha = alpha
-                                if (KeyguardBottomAreaRefactor.isEnabled) {
-                                    childViews[statusViewId]?.alpha = alpha
-                                    childViews[burnInLayerId]?.alpha = alpha
-                                }
+                    launch("$TAG#alpha") {
+                        viewModel.alpha(viewState).collect { alpha ->
+                            view.alpha = alpha
+                            if (KeyguardBottomAreaRefactor.isEnabled) {
+                                childViews[statusViewId]?.alpha = alpha
+                                childViews[burnInLayerId]?.alpha = alpha
                             }
                         }
                     }
@@ -223,7 +219,6 @@
                                                 indicationArea,
                                                 startButton,
                                                 endButton,
-                                                lockIcon,
                                                 deviceEntryIcon -> {
                                                     // Do not move these views
                                                 }
@@ -622,12 +617,11 @@
     private val statusViewId = R.id.keyguard_status_view
     private val burnInLayerId = R.id.burn_in_layer
     private val aodNotificationIconContainerId = R.id.aod_notification_icon_container
-    private val largeClockId = R.id.lockscreen_clock_view_large
-    private val smallClockId = R.id.lockscreen_clock_view
+    private val largeClockId = customR.id.lockscreen_clock_view_large
+    private val smallClockId = customR.id.lockscreen_clock_view
     private val indicationArea = R.id.keyguard_indication_area
     private val startButton = R.id.start_button
     private val endButton = R.id.end_button
-    private val lockIcon = R.id.lock_icon_view
     private val deviceEntryIcon = R.id.device_entry_icon_view
     private val nsslPlaceholderId = R.id.nssl_placeholder
     private val authInteractionProperties = AuthInteractionProperties()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index cef9a4e..dd8980d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -17,7 +17,6 @@
 
 package com.android.systemui.keyguard.ui.preview
 
-import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import android.app.WallpaperColors
 import android.content.BroadcastReceiver
 import android.content.Context
@@ -48,6 +47,7 @@
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import androidx.core.view.isInvisible
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
 import com.android.internal.policy.SystemBarUtils
 import com.android.keyguard.ClockEventController
 import com.android.keyguard.KeyguardClockSwitch
@@ -151,10 +151,7 @@
     private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
     private val height: Int = bundle.getInt(KEY_VIEW_HEIGHT)
     private val shouldHighlightSelectedAffordance: Boolean =
-        bundle.getBoolean(
-            KeyguardPreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES,
-            false,
-        )
+        bundle.getBoolean(KeyguardPreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES, false)
 
     private val displayId = bundle.getInt(KEY_DISPLAY_ID, DEFAULT_DISPLAY)
     private val display: Display? = displayManager.getDisplay(displayId)
@@ -188,24 +185,26 @@
     private var themeStyle: Style? = null
 
     init {
-        coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job() + createCoroutineTracingContext("KeyguardPreviewRenderer"))
+        coroutineScope =
+            CoroutineScope(
+                applicationScope.coroutineContext +
+                    Job() +
+                    createCoroutineTracingContext("KeyguardPreviewRenderer")
+            )
         disposables += DisposableHandle { coroutineScope.cancel() }
         clockController.setFallbackWeatherData(WeatherData.getPlaceholderWeatherData())
 
         if (KeyguardBottomAreaRefactor.isEnabled) {
             quickAffordancesCombinedViewModel.enablePreviewMode(
                 initiallySelectedSlotId =
-                    bundle.getString(
-                        KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
-                    ) ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                    bundle.getString(KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID)
+                        ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
                 shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
             )
         } else {
             bottomAreaViewModel.enablePreviewMode(
                 initiallySelectedSlotId =
-                    bundle.getString(
-                        KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
-                    ),
+                    bundle.getString(KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID),
                 shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
             )
         }
@@ -218,7 +217,7 @@
                     context,
                     displayManager.getDisplay(DEFAULT_DISPLAY),
                     if (hostToken == null) null else InputTransferToken(hostToken),
-                    "KeyguardPreviewRenderer"
+                    "KeyguardPreviewRenderer",
                 )
             disposables += DisposableHandle { host.release() }
         }
@@ -247,12 +246,12 @@
             rootView.measure(
                 View.MeasureSpec.makeMeasureSpec(
                     displayInfo?.logicalWidth ?: windowManager.currentWindowMetrics.bounds.width(),
-                    View.MeasureSpec.EXACTLY
+                    View.MeasureSpec.EXACTLY,
                 ),
                 View.MeasureSpec.makeMeasureSpec(
                     displayInfo?.logicalHeight
                         ?: windowManager.currentWindowMetrics.bounds.height(),
-                    View.MeasureSpec.EXACTLY
+                    View.MeasureSpec.EXACTLY,
                 ),
             )
             rootView.layout(0, 0, rootView.measuredWidth, rootView.measuredHeight)
@@ -278,9 +277,7 @@
         }
     }
 
-    fun onStartCustomizingQuickAffordances(
-        initiallySelectedSlotId: String?,
-    ) {
+    fun onStartCustomizingQuickAffordances(initiallySelectedSlotId: String?) {
         quickAffordancesCombinedViewModel.enablePreviewMode(
             initiallySelectedSlotId = initiallySelectedSlotId,
             shouldHighlightSelectedAffordance = true,
@@ -379,15 +376,9 @@
     @Deprecated("Deprecated as part of b/278057014")
     private fun setUpBottomArea(parentView: ViewGroup) {
         val bottomAreaView =
-            LayoutInflater.from(context)
-                .inflate(
-                    R.layout.keyguard_bottom_area,
-                    parentView,
-                    false,
-                ) as KeyguardBottomAreaView
-        bottomAreaView.init(
-            viewModel = bottomAreaViewModel,
-        )
+            LayoutInflater.from(context).inflate(R.layout.keyguard_bottom_area, parentView, false)
+                as KeyguardBottomAreaView
+        bottomAreaView.init(viewModel = bottomAreaViewModel)
         parentView.addView(
             bottomAreaView,
             FrameLayout.LayoutParams(
@@ -433,7 +424,7 @@
 
         setUpUdfps(
             previewContext,
-            if (MigrateClocksToBlueprint.isEnabled) keyguardRootView else rootView
+            if (MigrateClocksToBlueprint.isEnabled) keyguardRootView else rootView,
         )
 
         if (KeyguardBottomAreaRefactor.isEnabled) {
@@ -466,7 +457,7 @@
                 previewContext,
                 it,
                 previewInSplitShade(),
-                smartspaceViewModel
+                smartspaceViewModel,
             )
         }
         setupCommunalTutorialIndicator(keyguardRootView)
@@ -515,23 +506,20 @@
 
         val finger =
             LayoutInflater.from(previewContext)
-                .inflate(
-                    R.layout.udfps_keyguard_preview,
-                    parentView,
-                    false,
-                ) as View
+                .inflate(R.layout.udfps_keyguard_preview, parentView, false) as View
 
         // Place the UDFPS view in the proper sensor location
         if (MigrateClocksToBlueprint.isEnabled) {
-            finger.id = R.id.lock_icon_view
+            val lockId = KeyguardPreviewClockViewBinder.lockId
+            finger.id = lockId
             parentView.addView(finger)
             val cs = ConstraintSet()
             cs.clone(parentView as ConstraintLayout)
             cs.apply {
-                constrainWidth(R.id.lock_icon_view, sensorBounds.width())
-                constrainHeight(R.id.lock_icon_view, sensorBounds.height())
-                connect(R.id.lock_icon_view, TOP, PARENT_ID, TOP, sensorBounds.top)
-                connect(R.id.lock_icon_view, START, PARENT_ID, START, sensorBounds.left)
+                constrainWidth(lockId, sensorBounds.width())
+                constrainHeight(lockId, sensorBounds.height())
+                connect(lockId, TOP, PARENT_ID, TOP, sensorBounds.top)
+                connect(lockId, START, PARENT_ID, START, sensorBounds.left)
             }
             cs.applyTo(parentView)
         } else {
@@ -541,7 +529,7 @@
                 sensorBounds.left,
                 sensorBounds.top,
                 sensorBounds.right,
-                sensorBounds.bottom
+                sensorBounds.bottom,
             )
             parentView.addView(finger, fingerprintLayoutParams)
         }
@@ -565,7 +553,7 @@
                     FrameLayout.LayoutParams.WRAP_CONTENT,
                     resources.getDimensionPixelSize(
                         com.android.systemui.customization.R.dimen.small_clock_height
-                    )
+                    ),
                 )
             layoutParams.topMargin =
                 SystemBarUtils.getStatusBarHeight(previewContext) +
@@ -579,7 +567,7 @@
                 ),
                 /* top = */ 0,
                 /* end = */ 0,
-                /* bottom = */ 0
+                /* bottom = */ 0,
             )
             smallClockHostView.clipChildren = false
             parentView.addView(smallClockHostView)
@@ -703,9 +691,7 @@
     private suspend fun fetchThemeStyleFromSetting(): Style {
         val overlayPackageJson =
             withContext(backgroundDispatcher) {
-                secureSettings.getString(
-                    Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
-                )
+                secureSettings.getString(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES)
             }
         return if (!overlayPackageJson.isNullOrEmpty()) {
             try {
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 d745522..ee4f41d 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
@@ -30,7 +30,7 @@
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import androidx.constraintlayout.widget.ConstraintSet.VISIBLE
 import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
-import com.android.systemui.customization.R as custR
+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
@@ -153,7 +153,7 @@
                 R.id.weather_clock_bc_smartspace_bottom,
                 Barrier.BOTTOM,
                 getDimen(ENHANCED_SMARTSPACE_HEIGHT),
-                (custR.id.weather_clock_time),
+                (customR.id.weather_clock_time),
             )
             if (
                 rootViewModel.isNotifIconContainerVisible.value.value &&
@@ -184,40 +184,40 @@
             if (keyguardClockViewModel.clockShouldBeCentered.value) PARENT_ID
             else R.id.split_shade_guideline
         constraints.apply {
-            connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START)
-            connect(R.id.lockscreen_clock_view_large, END, guideline, END)
-            connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.device_entry_icon_view, TOP)
+            connect(customR.id.lockscreen_clock_view_large, START, PARENT_ID, START)
+            connect(customR.id.lockscreen_clock_view_large, END, guideline, END)
+            connect(customR.id.lockscreen_clock_view_large, BOTTOM, R.id.device_entry_icon_view, TOP)
             val largeClockTopMargin =
                 keyguardClockViewModel.getLargeClockTopMargin() +
                     getDimen(DATE_WEATHER_VIEW_HEIGHT) +
                     getDimen(ENHANCED_SMARTSPACE_HEIGHT)
-            connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
-            constrainWidth(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
+            connect(customR.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
+            constrainWidth(customR.id.lockscreen_clock_view_large, WRAP_CONTENT)
 
             // The following two lines make lockscreen_clock_view_large is constrained to available
             // height when it goes beyond constraints; otherwise, it use WRAP_CONTENT
-            constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
-            constrainMaxHeight(R.id.lockscreen_clock_view_large, 0)
-            constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT)
+            constrainHeight(customR.id.lockscreen_clock_view_large, WRAP_CONTENT)
+            constrainMaxHeight(customR.id.lockscreen_clock_view_large, 0)
+            constrainWidth(customR.id.lockscreen_clock_view, WRAP_CONTENT)
             constrainHeight(
-                R.id.lockscreen_clock_view,
-                context.resources.getDimensionPixelSize(custR.dimen.small_clock_height),
+                customR.id.lockscreen_clock_view,
+                context.resources.getDimensionPixelSize(customR.dimen.small_clock_height),
             )
             connect(
-                R.id.lockscreen_clock_view,
+                customR.id.lockscreen_clock_view,
                 START,
                 PARENT_ID,
                 START,
-                context.resources.getDimensionPixelSize(custR.dimen.clock_padding_start) +
+                context.resources.getDimensionPixelSize(customR.dimen.clock_padding_start) +
                     context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal),
             )
             val smallClockTopMargin = keyguardClockViewModel.getSmallClockTopMargin()
             create(R.id.small_clock_guideline_top, ConstraintSet.HORIZONTAL_GUIDELINE)
             setGuidelineBegin(R.id.small_clock_guideline_top, smallClockTopMargin)
-            connect(R.id.lockscreen_clock_view, TOP, R.id.small_clock_guideline_top, BOTTOM)
+            connect(customR.id.lockscreen_clock_view, TOP, R.id.small_clock_guideline_top, BOTTOM)
 
             // Explicitly clear pivot to force recalculate pivot instead of using legacy value
-            setTransformPivot(R.id.lockscreen_clock_view_large, Float.NaN, Float.NaN)
+            setTransformPivot(customR.id.lockscreen_clock_view_large, Float.NaN, Float.NaN)
 
             val smallClockBottom =
                 keyguardClockViewModel.getSmallClockTopMargin() +
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 782d37b..8d2bfb5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -27,11 +27,8 @@
 import androidx.annotation.VisibleForTesting
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
-import com.android.keyguard.LockIconView
-import com.android.keyguard.LockIconViewController
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
@@ -66,7 +63,6 @@
     private val context: Context,
     private val notificationPanelView: NotificationPanelView,
     private val featureFlags: FeatureFlags,
-    private val lockIconViewController: Lazy<LockIconViewController>,
     private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>,
     private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>,
     private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
@@ -78,70 +74,44 @@
     private var disposableHandle: DisposableHandle? = null
 
     override fun addViews(constraintLayout: ConstraintLayout) {
-        if (
-            !KeyguardBottomAreaRefactor.isEnabled &&
-                !MigrateClocksToBlueprint.isEnabled &&
-                !DeviceEntryUdfpsRefactor.isEnabled
-        ) {
+        if (!KeyguardBottomAreaRefactor.isEnabled && !MigrateClocksToBlueprint.isEnabled) {
             return
         }
 
-        notificationPanelView.findViewById<View>(R.id.lock_icon_view).let {
-            notificationPanelView.removeView(it)
-        }
-
         val view =
-            if (DeviceEntryUdfpsRefactor.isEnabled) {
-                DeviceEntryIconView(
-                        context,
-                        null,
-                        logger =
-                            LongPressHandlingViewLogger(
-                                logBuffer = logBuffer,
-                                TAG
-                            )
-                    )
-                    .apply { id = deviceEntryIconViewId }
-            } else {
-                // KeyguardBottomAreaRefactor.isEnabled or MigrateClocksToBlueprint.isEnabled
-                LockIconView(context, null).apply { id = R.id.lock_icon_view }
-            }
+            DeviceEntryIconView(
+                    context,
+                    null,
+                    logger = LongPressHandlingViewLogger(logBuffer = logBuffer, TAG),
+                )
+                .apply { id = deviceEntryIconViewId }
+
         constraintLayout.addView(view)
     }
 
     override fun bindData(constraintLayout: ConstraintLayout) {
-        if (DeviceEntryUdfpsRefactor.isEnabled) {
-            constraintLayout.findViewById<DeviceEntryIconView?>(deviceEntryIconViewId)?.let {
-                disposableHandle?.dispose()
-                disposableHandle =
-                    DeviceEntryIconViewBinder.bind(
-                        applicationScope,
-                        it,
-                        deviceEntryIconViewModel.get(),
-                        deviceEntryForegroundViewModel.get(),
-                        deviceEntryBackgroundViewModel.get(),
-                        falsingManager.get(),
-                        vibratorHelper.get(),
-                    )
-            }
-        } else {
-            constraintLayout.findViewById<LockIconView?>(R.id.lock_icon_view)?.let {
-                lockIconViewController.get().setLockIconView(it)
-            }
+        constraintLayout.findViewById<DeviceEntryIconView?>(deviceEntryIconViewId)?.let {
+            disposableHandle?.dispose()
+            disposableHandle =
+                DeviceEntryIconViewBinder.bind(
+                    applicationScope,
+                    it,
+                    deviceEntryIconViewModel.get(),
+                    deviceEntryForegroundViewModel.get(),
+                    deviceEntryBackgroundViewModel.get(),
+                    falsingManager.get(),
+                    vibratorHelper.get(),
+                )
         }
     }
 
     override fun applyConstraints(constraintSet: ConstraintSet) {
-        val isUdfpsSupported =
-            if (DeviceEntryUdfpsRefactor.isEnabled) {
-                Log.d(
-                    "DefaultDeviceEntrySection",
-                    "isUdfpsSupported=${deviceEntryIconViewModel.get().isUdfpsSupported.value}"
-                )
-                deviceEntryIconViewModel.get().isUdfpsSupported.value
-            } else {
-                authController.isUdfpsSupported
-            }
+        Log.d(
+            "DefaultDeviceEntrySection",
+            "isUdfpsSupported=${deviceEntryIconViewModel.get().isUdfpsSupported.value}",
+        )
+        val isUdfpsSupported = deviceEntryIconViewModel.get().isUdfpsSupported.value
+
         val scaleFactor: Float = authController.scaleFactor
         val mBottomPaddingPx =
             context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
@@ -160,31 +130,24 @@
         val iconRadiusPx = (defaultDensity * 36).toInt()
 
         if (isUdfpsSupported) {
-            if (DeviceEntryUdfpsRefactor.isEnabled) {
-                deviceEntryIconViewModel.get().udfpsLocation.value?.let { udfpsLocation ->
-                    Log.d(
-                        "DeviceEntrySection",
-                        "udfpsLocation=$udfpsLocation, " +
-                            "scaledLocation=(${udfpsLocation.centerX},${udfpsLocation.centerY}), " +
-                            "unusedAuthController=${authController.udfpsLocation}"
-                    )
-                    centerIcon(
-                        Point(udfpsLocation.centerX.toInt(), udfpsLocation.centerY.toInt()),
-                        udfpsLocation.radius,
-                        constraintSet
-                    )
-                }
-            } else {
-                authController.udfpsLocation?.let { udfpsLocation ->
-                    Log.d("DeviceEntrySection", "udfpsLocation=$udfpsLocation")
-                    centerIcon(udfpsLocation, authController.udfpsRadius, constraintSet)
-                }
+            deviceEntryIconViewModel.get().udfpsLocation.value?.let { udfpsLocation ->
+                Log.d(
+                    "DeviceEntrySection",
+                    "udfpsLocation=$udfpsLocation, " +
+                        "scaledLocation=(${udfpsLocation.centerX},${udfpsLocation.centerY}), " +
+                        "unusedAuthController=${authController.udfpsLocation}",
+                )
+                centerIcon(
+                    Point(udfpsLocation.centerX.toInt(), udfpsLocation.centerY.toInt()),
+                    udfpsLocation.radius,
+                    constraintSet,
+                )
             }
         } else {
             centerIcon(
                 Point(
                     (widthPixels / 2).toInt(),
-                    (heightPixels - ((mBottomPaddingPx + iconRadiusPx) * scaleFactor)).toInt()
+                    (heightPixels - ((mBottomPaddingPx + iconRadiusPx) * scaleFactor)).toInt(),
                 ),
                 iconRadiusPx * scaleFactor,
                 constraintSet,
@@ -193,12 +156,8 @@
     }
 
     override fun removeViews(constraintLayout: ConstraintLayout) {
-        if (DeviceEntryUdfpsRefactor.isEnabled) {
-            constraintLayout.removeView(deviceEntryIconViewId)
-            disposableHandle?.dispose()
-        } else {
-            constraintLayout.removeView(R.id.lock_icon_view)
-        }
+        constraintLayout.removeView(deviceEntryIconViewId)
+        disposableHandle?.dispose()
     }
 
     @VisibleForTesting
@@ -213,12 +172,7 @@
                 )
             }
 
-        val iconId =
-            if (DeviceEntryUdfpsRefactor.isEnabled) {
-                deviceEntryIconViewId
-            } else {
-                R.id.lock_icon_view
-            }
+        val iconId = deviceEntryIconViewId
 
         constraintSet.apply {
             constrainWidth(iconId, sensorRect.right - sensorRect.left)
@@ -228,14 +182,14 @@
                 ConstraintSet.TOP,
                 ConstraintSet.PARENT_ID,
                 ConstraintSet.TOP,
-                sensorRect.top
+                sensorRect.top,
             )
             connect(
                 iconId,
                 ConstraintSet.START,
                 ConstraintSet.PARENT_ID,
                 ConstraintSet.START,
-                sensorRect.left
+                sensorRect.left,
             )
         }
 
@@ -243,8 +197,8 @@
         // Without this logic, the lock icon location changes but the KeyguardBottomAreaView is not
         // updated and visible ui layout jank occurs. This is due to AmbientIndicationContainer
         // being in NPVC and laying out prior to the KeyguardRootView.
-        // Remove when both DeviceEntryUdfpsRefactor and KeyguardBottomAreaRefactor are enabled.
-        if (DeviceEntryUdfpsRefactor.isEnabled && !KeyguardBottomAreaRefactor.isEnabled) {
+        // Remove when KeyguardBottomAreaRefactor is enabled.
+        if (!KeyguardBottomAreaRefactor.isEnabled) {
             with(notificationPanelView) {
                 val isUdfpsSupported = deviceEntryIconViewModel.get().isUdfpsSupported.value
                 val bottomAreaViewRight = findViewById<View>(R.id.keyguard_bottom_area)?.right ?: 0
@@ -256,7 +210,7 @@
                             ambientLeft,
                             sensorRect.bottom,
                             bottomAreaViewRight - ambientLeft,
-                            ambientTop + it.measuredHeight
+                            ambientTop + it.measuredHeight,
                         )
                     } else {
                         // make bottom of ambient indication view the top of the lock icon
@@ -264,7 +218,7 @@
                             ambientLeft,
                             sensorRect.top - it.measuredHeight,
                             bottomAreaViewRight - ambientLeft,
-                            sensorRect.top
+                            sensorRect.top,
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt
index b33d552..604318a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt
@@ -22,6 +22,7 @@
 import androidx.constraintlayout.widget.Barrier
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.customization.R as customR
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.res.R
@@ -67,7 +68,7 @@
             connect(
                 R.id.keyguard_slice_view,
                 ConstraintSet.TOP,
-                R.id.lockscreen_clock_view,
+                customR.id.lockscreen_clock_view,
                 ConstraintSet.BOTTOM
             )
 
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 edcf97a..620cc13 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
@@ -55,11 +55,7 @@
                 R.id.nssl_placeholder_barrier_bottom,
                 Barrier.TOP,
                 0,
-                *intArrayOf(
-                    R.id.device_entry_icon_view,
-                    R.id.lock_icon_view,
-                    R.id.ambient_indication_container
-                )
+                *intArrayOf(R.id.device_entry_icon_view, R.id.ambient_indication_container),
             )
             connect(placeHolderId, BOTTOM, R.id.nssl_placeholder_barrier_bottom, TOP)
         }
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 99160f8..6ddcae3 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
@@ -23,6 +23,7 @@
 import androidx.constraintlayout.widget.Barrier
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
+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
@@ -157,7 +158,7 @@
                 connect(
                     sharedR.id.date_smartspace_view,
                     ConstraintSet.TOP,
-                    R.id.lockscreen_clock_view,
+                    customR.id.lockscreen_clock_view,
                     ConstraintSet.BOTTOM
                 )
                 connect(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
index 4d914c7..c11005d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
@@ -29,6 +29,7 @@
 import android.view.ViewGroup
 import android.view.ViewTreeObserver.OnPreDrawListener
 import com.android.app.animation.Interpolators
+import com.android.systemui.customization.R as customR
 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
 import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.SmartspaceMoveTransition.Companion.STATUS_AREA_MOVE_DOWN_MILLIS
@@ -242,11 +243,11 @@
                 }
                     ?: run {
                         Log.e(TAG, "No large clock set, falling back")
-                        addTarget(R.id.lockscreen_clock_view_large)
+                        addTarget(customR.id.lockscreen_clock_view_large)
                     }
             } else {
                 if (DEBUG) Log.i(TAG, "Adding small clock")
-                addTarget(R.id.lockscreen_clock_view)
+                addTarget(customR.id.lockscreen_clock_view)
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 700253ba..c3de067 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -61,6 +61,9 @@
 
     private static final int USER_ID_NOT_SPECIFIED = -1;
     protected static final int NOTIF_BASE_ID = 4273;
+    protected static final int NOTIF_GROUP_ID_SAVED = NOTIF_BASE_ID + 1;
+    protected static final int NOTIF_GROUP_ID_ERROR_SAVING = NOTIF_BASE_ID + 2;
+    protected static final int NOTIF_GROUP_ID_ERROR_STARTING = NOTIF_BASE_ID + 3;
     private static final String TAG = "RecordingService";
     private static final String CHANNEL_ID = "screen_record";
     @VisibleForTesting static final String GROUP_KEY_SAVED = "screen_record_saved";
@@ -280,7 +283,11 @@
      */
     @VisibleForTesting
     protected void createErrorStartingNotification(UserHandle currentUser) {
-        createErrorNotification(currentUser, strings().getStartError(), GROUP_KEY_ERROR_STARTING);
+        createErrorNotification(
+                currentUser,
+                strings().getStartError(),
+                GROUP_KEY_ERROR_STARTING,
+                NOTIF_GROUP_ID_ERROR_STARTING);
     }
 
     /**
@@ -289,13 +296,21 @@
      */
     @VisibleForTesting
     protected void createErrorSavingNotification(UserHandle currentUser) {
-        createErrorNotification(currentUser, strings().getSaveError(), GROUP_KEY_ERROR_SAVING);
+        createErrorNotification(
+                currentUser,
+                strings().getSaveError(),
+                GROUP_KEY_ERROR_SAVING,
+                NOTIF_GROUP_ID_ERROR_SAVING);
     }
 
     private void createErrorNotification(
-            UserHandle currentUser, String notificationContentTitle, String groupKey) {
+            UserHandle currentUser,
+            String notificationContentTitle,
+            String groupKey,
+            int notificationIdForGroup) {
         // Make sure error notifications get their own group.
-        postGroupSummaryNotification(currentUser, notificationContentTitle, groupKey);
+        postGroupSummaryNotification(
+                currentUser, notificationContentTitle, groupKey, notificationIdForGroup);
 
         Bundle extras = new Bundle();
         extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle());
@@ -410,17 +425,20 @@
     }
 
     /**
-     * Adds a group summary notification for save notifications so that save notifications from
-     * multiple recordings are grouped together, and the foreground service recording notification
-     * is not.
+     * Posts a group summary notification for the given group.
+     *
+     * Notifications that should be grouped:
+     *  - Save notifications
+     *  - Error saving notifications
+     *  - Error starting notifications
+     *
+     * The foreground service recording notification should never be grouped.
      */
-    private void postGroupSummaryNotificationForSaves(UserHandle currentUser) {
-        postGroupSummaryNotification(currentUser, strings().getSaveTitle(), GROUP_KEY_SAVED);
-    }
-
-    /** Posts a group summary notification for the given group. */
     private void postGroupSummaryNotification(
-            UserHandle currentUser, String notificationContentTitle, String groupKey) {
+            UserHandle currentUser,
+            String notificationContentTitle,
+            String groupKey,
+            int notificationIdForGroup) {
         Bundle extras = new Bundle();
         extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
                 strings().getTitle());
@@ -431,7 +449,8 @@
                 .setGroupSummary(true)
                 .setExtras(extras)
                 .build();
-        mNotificationManager.notifyAsUser(getTag(), mNotificationId, groupNotif, currentUser);
+        mNotificationManager.notifyAsUser(
+                getTag(), notificationIdForGroup, groupNotif, currentUser);
     }
 
     private void stopService() {
@@ -484,7 +503,11 @@
                 Log.d(getTag(), "saving recording");
                 Notification notification = createSaveNotification(
                         getRecorder() != null ? getRecorder().save() : null);
-                postGroupSummaryNotificationForSaves(currentUser);
+                postGroupSummaryNotification(
+                        currentUser,
+                        strings().getSaveTitle(),
+                        GROUP_KEY_SAVED,
+                        NOTIF_GROUP_ID_SAVED);
                 mNotificationManager.notifyAsUser(null, mNotificationId,  notification,
                         currentUser);
             } catch (IOException | IllegalStateException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index b7a95e9..0e30f2b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -38,7 +38,7 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.window.StatusBarWindowController;
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
 
 import dagger.Lazy;
 
@@ -62,7 +62,7 @@
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final StatusBarStateController mStatusBarStateController;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    private final StatusBarWindowController mStatusBarWindowController;
+    private final StatusBarWindowControllerStore mStatusBarWindowControllerStore;
     private final DeviceProvisionedController mDeviceProvisionedController;
 
     private final Lazy<NotificationShadeWindowViewController> mNotifShadeWindowViewController;
@@ -83,7 +83,7 @@
             KeyguardStateController keyguardStateController,
             StatusBarStateController statusBarStateController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            StatusBarWindowController statusBarWindowController,
+            StatusBarWindowControllerStore statusBarWindowControllerStore,
             DeviceProvisionedController deviceProvisionedController,
             NotificationShadeWindowController notificationShadeWindowController,
             @DisplayId int displayId,
@@ -102,7 +102,7 @@
         mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
         mNpvc = shadeViewControllerLazy;
         mStatusBarStateController = statusBarStateController;
-        mStatusBarWindowController = statusBarWindowController;
+        mStatusBarWindowControllerStore = statusBarWindowControllerStore;
         mDeviceProvisionedController = deviceProvisionedController;
         mGutsManager = gutsManager;
         mNotificationShadeWindowController = notificationShadeWindowController;
@@ -315,7 +315,7 @@
 
         // Update the visibility of notification shade and status bar window.
         mNotificationShadeWindowController.setPanelVisible(false);
-        mStatusBarWindowController.setForceStatusBarVisible(false);
+        mStatusBarWindowControllerStore.getDefaultDisplay().setForceStatusBarVisible(false);
 
         // Close any guts that might be visible
         mGutsManager.get().closeAndSaveGuts(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
index 3ad76b7..7eff812 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
@@ -18,7 +18,6 @@
 import android.app.Fragment
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.fragments.FragmentHostManager
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
@@ -27,9 +26,11 @@
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
-import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import java.lang.IllegalStateException
-import javax.inject.Inject
 import javax.inject.Provider
 
 /**
@@ -65,13 +66,17 @@
             statusBarTransitions: PhoneStatusBarTransitions,
         )
     }
+
+    interface Factory {
+        fun create(displayId: Int): StatusBarInitializer
+    }
 }
 
-@SysUISingleton
 class StatusBarInitializerImpl
-@Inject
+@AssistedInject
 constructor(
-    private val windowController: StatusBarWindowController,
+    @Assisted private val displayId: Int,
+    private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
     private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
     private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>,
 ) : CoreStartable, StatusBarInitializer {
@@ -106,7 +111,7 @@
 
     private fun doStart() {
         initialized = true
-        windowController.fragmentHostManager
+        statusBarWindowControllerStore.defaultDisplay.fragmentHostManager
             .addTagListener(
                 CollapsedStatusBarFragment.TAG,
                 object : FragmentHostManager.FragmentListener {
@@ -137,4 +142,9 @@
             )
             .commit()
     }
+
+    @AssistedFactory
+    interface Factory : StatusBarInitializer.Factory {
+        override fun create(displayId: Int): StatusBarInitializerImpl
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
new file mode 100644
index 0000000..8d044bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.core
+
+import android.view.Display
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/** Provides per display instances of [StatusBarInitializer]. */
+interface StatusBarInitializerStore {
+    /**
+     * The instance for the default/main display of the device. For example, on a phone or a tablet,
+     * the default display is the internal/built-in display of the device.
+     *
+     * Note that the id of the default display is [Display.DEFAULT_DISPLAY].
+     */
+    val defaultDisplay: StatusBarInitializer
+
+    /**
+     * Returns an instance for a specific display id.
+     *
+     * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing
+     *   displays.
+     */
+    fun forDisplay(displayId: Int): StatusBarInitializer
+}
+
+@SysUISingleton
+class MultiDisplayStatusBarInitializerStore
+@Inject
+constructor(
+    @Background private val backgroundApplicationScope: CoroutineScope,
+    private val factory: StatusBarInitializer.Factory,
+    private val displayRepository: DisplayRepository,
+) : StatusBarInitializerStore, CoreStartable {
+
+    init {
+        StatusBarConnectedDisplays.assertInNewMode()
+    }
+
+    private val perDisplayInitializers = ConcurrentHashMap<Int, StatusBarInitializer>()
+
+    override val defaultDisplay: StatusBarInitializer
+        get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+    override fun forDisplay(displayId: Int): StatusBarInitializer {
+        if (displayRepository.getDisplay(displayId) == null) {
+            throw IllegalArgumentException("Display with id $displayId doesn't exist.")
+        }
+        return perDisplayInitializers.computeIfAbsent(displayId) { factory.create(displayId) }
+    }
+
+    override fun start() {
+        backgroundApplicationScope.launch(
+            CoroutineName("MultiDisplayStatusBarInitializerStore#start")
+        ) {
+            displayRepository.displayRemovalEvent.collect { removedDisplayId ->
+                perDisplayInitializers.remove(removedDisplayId)
+            }
+        }
+    }
+}
+
+@SysUISingleton
+class SingleDisplayStatusBarInitializerStore
+@Inject
+constructor(factory: StatusBarInitializerImpl.Factory) : StatusBarInitializerStore {
+
+    init {
+        StatusBarConnectedDisplays.assertInLegacyMode()
+    }
+
+    private val defaultInstance = factory.create(Display.DEFAULT_DISPLAY)
+
+    override val defaultDisplay: StatusBarInitializer = defaultInstance
+
+    override fun forDisplay(displayId: Int): StatusBarInitializer = defaultDisplay
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
index 8bd990b..d372eb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
@@ -36,7 +36,7 @@
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
-import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.statusbar.window.data.model.StatusBarWindowState
 import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
 import com.android.wm.shell.bubbles.Bubbles
@@ -63,8 +63,8 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     private val statusBarInitializer: StatusBarInitializer,
-    private val statusBarWindowController: StatusBarWindowController,
     private val statusBarModeRepository: StatusBarModeRepositoryStore,
+    private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
     private val demoModeController: DemoModeController,
     private val pluginDependencyProvider: PluginDependencyProvider,
     private val autoHideController: AutoHideController,
@@ -157,7 +157,7 @@
 
     private fun createAndAddWindow() {
         initializeStatusBarFragment()
-        statusBarWindowController.attach()
+        statusBarWindowControllerStore.defaultDisplay.attach()
     }
 
     private fun initializeStatusBarFragment() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index cd1642e..5aad11f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -77,16 +77,6 @@
 
         @Provides
         @SysUISingleton
-        fun defaultStatusBarWindowController(
-            context: Context,
-            viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
-            factory: StatusBarWindowControllerImpl.Factory,
-        ): StatusBarWindowController {
-            return factory.create(context, viewCaptureAwareWindowManager)
-        }
-
-        @Provides
-        @SysUISingleton
         fun windowControllerStore(
             multiDisplayImplLazy: Lazy<MultiDisplayStatusBarWindowControllerStore>,
             singleDisplayImplLazy: Lazy<SingleDisplayStatusBarWindowControllerStore>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index 118f5f0..bf7e879 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -34,7 +34,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
-import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.util.animation.AnimationUtil.Companion.frames
 import javax.inject.Inject
 import kotlin.math.roundToInt
@@ -44,8 +44,8 @@
  */
 class SystemEventChipAnimationController @Inject constructor(
     private val context: Context,
-    private val statusBarWindowController: StatusBarWindowController,
-    private val contentInsetsProvider: StatusBarContentInsetsProvider
+    private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
+    private val contentInsetsProvider: StatusBarContentInsetsProvider,
 ) : SystemStatusAnimationCallback {
 
     private lateinit var animationWindowView: FrameLayout
@@ -244,7 +244,7 @@
         val height = themedContext.resources.getDimensionPixelSize(R.dimen.status_bar_height)
         val lp = FrameLayout.LayoutParams(MATCH_PARENT, height)
         lp.gravity = Gravity.END or Gravity.TOP
-        statusBarWindowController.addViewToWindow(animationWindowView, lp)
+        statusBarWindowControllerStore.defaultDisplay.addViewToWindow(animationWindowView, lp)
         animationWindowView.clipToPadding = false
         animationWindowView.clipChildren = false
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index f0e60dd..e34f61d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -23,7 +23,7 @@
 import androidx.core.animation.AnimatorSet
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.util.Assert
 import com.android.systemui.util.time.SystemClock
 import java.io.PrintWriter
@@ -65,7 +65,7 @@
 constructor(
     private val coordinator: SystemEventCoordinator,
     private val chipAnimationController: SystemEventChipAnimationController,
-    private val statusBarWindowController: StatusBarWindowController,
+    private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
     dumpManager: DumpManager,
     private val systemClock: SystemClock,
     @Application private val coroutineScope: CoroutineScope,
@@ -277,7 +277,7 @@
     private fun runChipAppearAnimation() {
         Assert.isMainThread()
         if (hasPersistentDot) {
-            statusBarWindowController.setForceStatusBarVisible(true)
+            statusBarWindowControllerStore.defaultDisplay.setForceStatusBarVisible(true)
         }
         animationState.value = ANIMATING_IN
 
@@ -311,7 +311,7 @@
                             scheduledEvent.value != null -> ANIMATION_QUEUED
                             else -> IDLE
                         }
-                    statusBarWindowController.setForceStatusBarVisible(false)
+                    statusBarWindowControllerStore.defaultDisplay.setForceStatusBarVisible(false)
                 }
             }
         )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
index 693ae66..ebaa3c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
@@ -20,7 +20,7 @@
 import android.view.MotionEvent
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.settings.DisplayTracker
-import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import javax.inject.Inject
 
 /** A class to detect when a user swipes away the status bar. */
@@ -31,10 +31,11 @@
     context: Context,
     displayTracker: DisplayTracker,
     logger: SwipeUpGestureLogger,
-    private val statusBarWindowController: StatusBarWindowController,
+    private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
 ) : SwipeUpGestureHandler(context, displayTracker, logger, loggerTag = LOGGER_TAG) {
     override fun startOfGestureIsWithinBounds(ev: MotionEvent): Boolean {
         // Gesture starts just below the status bar
+        val statusBarWindowController = statusBarWindowControllerStore.defaultDisplay
         return ev.y >= statusBarWindowController.statusBarHeight &&
             ev.y <= 3 * statusBarWindowController.statusBarHeight
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
index 5b37468..d1338ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
@@ -58,7 +58,7 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
-import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.kotlin.getOrNull
@@ -93,7 +93,7 @@
     @Main private val mainExecutor: DelayableExecutor,
     private val shadeControllerLazy: Lazy<ShadeController>,
     private val communalSceneInteractor: CommunalSceneInteractor,
-    private val statusBarWindowController: StatusBarWindowController,
+    private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
     private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
     private val shadeAnimationInteractor: ShadeAnimationInteractor,
     private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>,
@@ -562,7 +562,7 @@
         }
         val rootView = animationController.transitionContainer.rootView
         val controllerFromStatusBar: Optional<ActivityTransitionAnimator.Controller> =
-            statusBarWindowController.wrapAnimationControllerIfInStatusBar(
+            statusBarWindowControllerStore.defaultDisplay.wrapAnimationControllerIfInStatusBar(
                 rootView,
                 animationController
             )
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 7e5b455..cb4454d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -226,7 +226,7 @@
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
-import com.android.systemui.statusbar.window.StatusBarWindowController;
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
 import com.android.systemui.util.DumpUtilsKt;
@@ -372,7 +372,7 @@
     @WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final StatusBarInitializer mStatusBarInitializer;
-    private final StatusBarWindowController mStatusBarWindowController;
+    private final StatusBarWindowControllerStore mStatusBarWindowControllerStore;
     private final StatusBarModeRepositoryStore mStatusBarModeRepository;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @VisibleForTesting
@@ -610,7 +610,7 @@
             LightBarController lightBarController,
             AutoHideController autoHideController,
             StatusBarInitializer statusBarInitializer,
-            StatusBarWindowController statusBarWindowController,
+            StatusBarWindowControllerStore statusBarWindowControllerStore,
             StatusBarWindowStateController statusBarWindowStateController,
             StatusBarModeRepositoryStore statusBarModeRepository,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -716,7 +716,7 @@
         mLightBarController = lightBarController;
         mAutoHideController = autoHideController;
         mStatusBarInitializer = statusBarInitializer;
-        mStatusBarWindowController = statusBarWindowController;
+        mStatusBarWindowControllerStore = statusBarWindowControllerStore;
         mStatusBarModeRepository = statusBarModeRepository;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mPulseExpansionHandler = pulseExpansionHandler;
@@ -1894,7 +1894,7 @@
         // When the StatusBarSimpleFragment flag is enabled, this logic will be done in
         // StatusBarOrchestrator
         if (!StatusBarSimpleFragment.isEnabled()) {
-            mStatusBarWindowController.attach();
+            mStatusBarWindowControllerStore.getDefaultDisplay().attach();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index 4604233..1cca3ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -57,7 +57,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.kotlin.getOrNull
 import dagger.Lazy
@@ -85,7 +85,7 @@
     private val context: Context,
     @DisplayId private val displayId: Int,
     private val lockScreenUserManager: NotificationLockscreenUserManager,
-    private val statusBarWindowController: StatusBarWindowController,
+    private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
     private val wakefulnessLifecycle: WakefulnessLifecycle,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val deviceProvisionedController: DeviceProvisionedController,
@@ -525,7 +525,7 @@
         }
         val rootView = animationController.transitionContainer.rootView
         val controllerFromStatusBar: Optional<ActivityTransitionAnimator.Controller> =
-            statusBarWindowController.wrapAnimationControllerIfInStatusBar(
+            statusBarWindowControllerStore.defaultDisplay.wrapAnimationControllerIfInStatusBar(
                 rootView,
                 animationController
             )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index d6716a0..e7d9717 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -40,7 +40,7 @@
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
-import com.android.systemui.statusbar.window.StatusBarWindowController;
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
 import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
 import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
 import com.android.systemui.util.leak.RotationUtils;
@@ -49,7 +49,7 @@
 
 public class PhoneStatusBarView extends FrameLayout {
     private static final String TAG = "PhoneStatusBarView";
-    private final StatusBarWindowController mStatusBarWindowController;
+    private final StatusBarWindowControllerStore mStatusBarWindowControllerStore;
 
     private int mRotationOrientation = -1;
     @Nullable
@@ -75,7 +75,7 @@
 
     public PhoneStatusBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
+        mStatusBarWindowControllerStore = Dependency.get(StatusBarWindowControllerStore.class);
     }
 
     void setTouchEventHandler(Gefingerpoken handler) {
@@ -326,7 +326,7 @@
         if (Flags.statusBarStopUpdatingWindowHeight()) {
             return;
         }
-        mStatusBarWindowController.refreshStatusBarHeight();
+        mStatusBarWindowControllerStore.getDefaultDisplay().refreshStatusBarHeight();
     }
 
     interface HasCornerCutoutFetcher {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 8f2d4f9..7145ffe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -28,7 +28,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-import com.android.systemui.statusbar.window.StatusBarWindowController;
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
 
 import javax.inject.Inject;
 
@@ -38,7 +38,7 @@
 @SysUISingleton
 public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener, CoreStartable {
     private final NotificationShadeWindowController mNotificationShadeWindowController;
-    private final StatusBarWindowController mStatusBarWindowController;
+    private final StatusBarWindowControllerStore mStatusBarWindowControllerStore;
     private final ShadeViewController mShadeViewController;
     private final PanelExpansionInteractor mPanelExpansionInteractor;
     private final NotificationStackScrollLayoutController mNsslController;
@@ -50,7 +50,7 @@
     @Inject
     StatusBarHeadsUpChangeListener(
             NotificationShadeWindowController notificationShadeWindowController,
-            StatusBarWindowController statusBarWindowController,
+            StatusBarWindowControllerStore statusBarWindowControllerStore,
             ShadeViewController shadeViewController,
             PanelExpansionInteractor panelExpansionInteractor,
             NotificationStackScrollLayoutController nsslController,
@@ -59,7 +59,7 @@
             StatusBarStateController statusBarStateController,
             NotificationRemoteInputManager notificationRemoteInputManager) {
         mNotificationShadeWindowController = notificationShadeWindowController;
-        mStatusBarWindowController = statusBarWindowController;
+        mStatusBarWindowControllerStore = statusBarWindowControllerStore;
         mShadeViewController = shadeViewController;
         mPanelExpansionInteractor = panelExpansionInteractor;
         mNsslController = nsslController;
@@ -78,7 +78,7 @@
     public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
         if (inPinnedMode) {
             mNotificationShadeWindowController.setHeadsUpShowing(true);
-            mStatusBarWindowController.setForceStatusBarVisible(true);
+            mStatusBarWindowControllerStore.getDefaultDisplay().setForceStatusBarVisible(true);
             if (mPanelExpansionInteractor.isFullyCollapsed()) {
                 mShadeViewController.updateTouchableRegion();
             }
@@ -93,7 +93,9 @@
                 // open artificially.
                 mNotificationShadeWindowController.setHeadsUpShowing(false);
                 if (bypassKeyguard) {
-                    mStatusBarWindowController.setForceStatusBarVisible(false);
+                    mStatusBarWindowControllerStore
+                            .getDefaultDisplay()
+                            .setForceStatusBarVisible(false);
                 }
             } else {
                 // we need to keep the panel open artificially, let's wait until the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index 5b03198..5f864e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -15,12 +15,18 @@
  */
 package com.android.systemui.statusbar.phone.dagger
 
+import android.view.Display
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Default
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.core.CommandQueueInitializer
+import com.android.systemui.statusbar.core.MultiDisplayStatusBarInitializerStore
+import com.android.systemui.statusbar.core.SingleDisplayStatusBarInitializerStore
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.core.StatusBarInitializer
 import com.android.systemui.statusbar.core.StatusBarInitializerImpl
+import com.android.systemui.statusbar.core.StatusBarInitializerStore
 import com.android.systemui.statusbar.core.StatusBarOrchestrator
 import com.android.systemui.statusbar.core.StatusBarSimpleFragment
 import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks
@@ -47,15 +53,31 @@
         impl: CentralSurfacesCommandQueueCallbacks
     ): CommandQueue.Callbacks
 
+    @Binds
+    fun initializerFactory(
+        implFactory: StatusBarInitializerImpl.Factory
+    ): StatusBarInitializer.Factory
+
     /** Binds {@link StatusBarInitializer} as a {@link CoreStartable}. */
     @Binds
     @IntoMap
     @ClassKey(StatusBarInitializerImpl::class)
-    fun bindStatusBarInitializer(impl: StatusBarInitializerImpl): CoreStartable
+    fun bindStatusBarInitializer(@Default impl: StatusBarInitializerImpl): CoreStartable
 
-    @Binds fun statusBarInitializer(impl: StatusBarInitializerImpl): StatusBarInitializer
+    @Binds fun statusBarInitializer(@Default impl: StatusBarInitializerImpl): StatusBarInitializer
 
     companion object {
+        // Dagger doesn't support providing AssistedInject types, without a qualifier. Using the
+        // Default qualifier for this reason.
+        @Default
+        @Provides
+        @SysUISingleton
+        fun statusBarInitializerImpl(
+            implFactory: StatusBarInitializerImpl.Factory
+        ): StatusBarInitializerImpl {
+            return implFactory.create(displayId = Display.DEFAULT_DISPLAY)
+        }
+
         @Provides
         @SysUISingleton
         @IntoMap
@@ -83,5 +105,32 @@
                 CoreStartable.NOP
             }
         }
+
+        @Provides
+        @SysUISingleton
+        @IntoMap
+        @ClassKey(StatusBarInitializerStore::class)
+        fun initializerStoreAsCoreStartable(
+            multiDisplayStoreLazy: Lazy<MultiDisplayStatusBarInitializerStore>
+        ): CoreStartable {
+            return if (StatusBarConnectedDisplays.isEnabled) {
+                multiDisplayStoreLazy.get()
+            } else {
+                CoreStartable.NOP
+            }
+        }
+
+        @Provides
+        @SysUISingleton
+        fun initializerStore(
+            singleDisplayStoreLazy: Lazy<SingleDisplayStatusBarInitializerStore>,
+            multiDisplayStoreLazy: Lazy<MultiDisplayStatusBarInitializerStore>,
+        ): StatusBarInitializerStore {
+            return if (StatusBarConnectedDisplays.isEnabled) {
+                multiDisplayStoreLazy.get()
+            } else {
+                singleDisplayStoreLazy.get()
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index f026b99..cf877a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -29,6 +29,7 @@
 import com.android.systemui.statusbar.phone.StatusBarLocation;
 import com.android.systemui.statusbar.policy.Clock;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
 
 import dagger.Module;
 import dagger.Provides;
@@ -125,9 +126,9 @@
     @StatusBarFragmentScope
     static PhoneStatusBarTransitions providePhoneStatusBarTransitions(
             @RootView PhoneStatusBarView view,
-            StatusBarWindowController statusBarWindowController
-    ) {
-        return new PhoneStatusBarTransitions(view, statusBarWindowController.getBackgroundView());
+            StatusBarWindowControllerStore statusBarWindowControllerStore) {
+        return new PhoneStatusBarTransitions(
+                view, statusBarWindowControllerStore.getDefaultDisplay().getBackgroundView());
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index bd6a1c0..3cf8c3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -52,7 +52,7 @@
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import com.android.systemui.statusbar.policy.CallbackController
-import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.util.time.SystemClock
 import java.io.PrintWriter
 import java.util.concurrent.Executor
@@ -75,7 +75,7 @@
     @Main private val mainExecutor: Executor,
     private val iActivityManager: IActivityManager,
     private val dumpManager: DumpManager,
-    private val statusBarWindowController: StatusBarWindowController,
+    private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
     private val swipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler,
     private val statusBarModeRepository: StatusBarModeRepositoryStore,
     @OngoingCallLog private val logger: LogBuffer,
@@ -205,7 +205,9 @@
         this.chipView = chipView
         val backgroundView: ChipBackgroundContainer? =
             chipView.findViewById(R.id.ongoing_activity_chip_background)
-        backgroundView?.maxHeightFetcher = { statusBarWindowController.statusBarHeight }
+        backgroundView?.maxHeightFetcher = {
+            statusBarWindowControllerStore.defaultDisplay.statusBarHeight
+        }
         if (hasOngoingCall()) {
             updateChip()
         }
@@ -339,7 +341,8 @@
             // But, this class still needs to do the non-display logic regardless of the flag.
             uidObserver.registerWithUid(currentCallNotificationInfo.uid)
             if (!currentCallNotificationInfo.statusBarSwipedAway) {
-                statusBarWindowController.setOngoingProcessRequiresStatusBarVisible(true)
+                statusBarWindowControllerStore.defaultDisplay
+                    .setOngoingProcessRequiresStatusBarVisible(true)
             }
             updateGestureListening()
             sendStateChangeEvent()
@@ -405,7 +408,9 @@
         if (!Flags.statusBarScreenSharingChips()) {
             tearDownChipView()
         }
-        statusBarWindowController.setOngoingProcessRequiresStatusBarVisible(false)
+        statusBarWindowControllerStore.defaultDisplay.setOngoingProcessRequiresStatusBarVisible(
+            false
+        )
         swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG)
         sendStateChangeEvent()
         uidObserver.unregister()
@@ -429,7 +434,9 @@
     private fun onSwipeAwayGestureDetected() {
         logger.log(TAG, LogLevel.DEBUG, {}, { "Swipe away gesture detected" })
         callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true)
-        statusBarWindowController.setOngoingProcessRequiresStatusBarVisible(false)
+        statusBarWindowControllerStore.defaultDisplay.setOngoingProcessRequiresStatusBarVisible(
+            false
+        )
         swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
index 5f30b37..7d0dadc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.window
 
+import android.content.Context
 import android.view.Display
 import android.view.WindowManager
 import com.android.app.viewcapture.ViewCaptureAwareWindowManager
@@ -105,12 +106,19 @@
 @SysUISingleton
 class SingleDisplayStatusBarWindowControllerStore
 @Inject
-constructor(private val controller: StatusBarWindowController) : StatusBarWindowControllerStore {
+constructor(
+    context: Context,
+    viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+    factory: StatusBarWindowControllerImpl.Factory,
+) : StatusBarWindowControllerStore {
 
     init {
         StatusBarConnectedDisplays.assertInLegacyMode()
     }
 
+    private val controller: StatusBarWindowController =
+        factory.create(context, viewCaptureAwareWindowManager)
+
     override val defaultDisplay = controller
 
     override fun forDisplay(displayId: Int) = controller
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index ae635b8..df50f76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -77,6 +77,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.RingerModeLiveData;
@@ -125,6 +126,7 @@
     @Mock private LightBarController mLightBarController;
     @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
     @Mock private StatusBarWindowController mStatusBarWindowController;
+    @Mock private StatusBarWindowControllerStore mStatusBarWindowControllerStore;
     @Mock private IWindowManager mWindowManager;
     @Mock private Executor mBackgroundExecutor;
     @Mock private UiEventLogger mUiEventLogger;
@@ -155,7 +157,8 @@
         when(mUserContextProvider.getUserContext()).thenReturn(mContext);
         when(mResources.getConfiguration()).thenReturn(
                 getContext().getResources().getConfiguration());
-
+        when(mStatusBarWindowControllerStore.getDefaultDisplay())
+                .thenReturn(mStatusBarWindowController);
         mGlobalSettings = new FakeGlobalSettings();
         mSecureSettings = new FakeSettings();
         mInteractor = mKosmos.getGlobalActionsInteractor();
@@ -184,7 +187,7 @@
                 mStatusBarService,
                 mLightBarController,
                 mNotificationShadeWindowController,
-                mStatusBarWindowController,
+                mStatusBarWindowControllerStore,
                 mWindowManager,
                 mBackgroundExecutor,
                 mUiEventLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index 96a0aad..ecc62e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -298,7 +298,7 @@
             underTest.applyDefaultConstraints(cs)
             val referencedIds =
                 cs.getReferencedIds(R.id.weather_clock_date_and_icons_barrier_bottom)
-            referencedIds.contentEquals(intArrayOf(R.id.lockscreen_clock_view))
+            referencedIds.contentEquals(intArrayOf(customR.id.lockscreen_clock_view))
         }
 
     @Test
@@ -323,7 +323,7 @@
         }
 
     private fun assertLargeClockTop(cs: ConstraintSet, expectedLargeClockTopMargin: Int) {
-        val largeClockConstraint = cs.getConstraint(R.id.lockscreen_clock_view_large)
+        val largeClockConstraint = cs.getConstraint(customR.id.lockscreen_clock_view_large)
         assertThat(largeClockConstraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
         assertThat(largeClockConstraint.layout.topMargin).isEqualTo(expectedLargeClockTopMargin)
     }
@@ -332,7 +332,7 @@
         val smallClockGuidelineConstraint = cs.getConstraint(R.id.small_clock_guideline_top)
         assertThat(smallClockGuidelineConstraint.layout.topToTop).isEqualTo(-1)
 
-        val smallClockConstraint = cs.getConstraint(R.id.lockscreen_clock_view)
+        val smallClockConstraint = cs.getConstraint(customR.id.lockscreen_clock_view)
         assertThat(smallClockConstraint.layout.topToBottom)
             .isEqualTo(R.id.small_clock_guideline_top)
         assertThat(smallClockConstraint.layout.topMargin).isEqualTo(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index bfb8a57..cea51a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -18,13 +18,11 @@
 package com.android.systemui.keyguard.ui.view.layout.sections
 
 import android.graphics.Point
-import android.platform.test.annotations.DisableFlags
 import android.view.WindowManager
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.keyguard.LegacyLockIconViewController
 import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
@@ -60,7 +58,6 @@
     @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager
     @Mock private lateinit var notificationPanelView: NotificationPanelView
     private lateinit var featureFlags: FakeFeatureFlags
-    @Mock private lateinit var lockIconViewController: LegacyLockIconViewController
     @Mock private lateinit var falsingManager: FalsingManager
     @Mock private lateinit var deviceEntryIconViewModel: DeviceEntryIconViewModel
     private lateinit var underTest: DefaultDeviceEntrySection
@@ -81,7 +78,6 @@
                 context,
                 notificationPanelView,
                 featureFlags,
-                { lockIconViewController },
                 { deviceEntryIconViewModel },
                 { mock(DeviceEntryForegroundViewModel::class.java) },
                 { mock(DeviceEntryBackgroundViewModel::class.java) },
@@ -102,37 +98,13 @@
     @Test
     fun addViewsConditionally_migrateAndRefactorFlagsOn() {
         mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
-        mSetFlagsRule.enableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
         val constraintLayout = ConstraintLayout(context, null)
         underTest.addViews(constraintLayout)
         assertThat(constraintLayout.childCount).isGreaterThan(0)
     }
 
     @Test
-    @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-    fun addViewsConditionally_migrateFlagOff() {
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
-        val constraintLayout = ConstraintLayout(context, null)
-        underTest.addViews(constraintLayout)
-        assertThat(constraintLayout.childCount).isEqualTo(0)
-    }
-
-    @Test
-    fun applyConstraints_udfps_refactor_off() {
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
-        val cs = ConstraintSet()
-        underTest.applyConstraints(cs)
-
-        val constraint = cs.getConstraint(R.id.lock_icon_view)
-
-        assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
-        assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
-    }
-
-    @Test
-    fun applyConstraints_udfps_refactor_on() {
-        mSetFlagsRule.enableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+    fun applyConstraints() {
         whenever(deviceEntryIconViewModel.isUdfpsSupported).thenReturn(MutableStateFlow(false))
         val cs = ConstraintSet()
         underTest.applyConstraints(cs)
@@ -144,24 +116,7 @@
     }
 
     @Test
-    fun testCenterIcon_udfps_refactor_off() {
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
-        val cs = ConstraintSet()
-        underTest.centerIcon(Point(5, 6), 1F, cs)
-
-        val constraint = cs.getConstraint(R.id.lock_icon_view)
-
-        assertThat(constraint.layout.mWidth).isEqualTo(2)
-        assertThat(constraint.layout.mHeight).isEqualTo(2)
-        assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
-        assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
-        assertThat(constraint.layout.topMargin).isEqualTo(5)
-        assertThat(constraint.layout.startMargin).isEqualTo(4)
-    }
-
-    @Test
-    fun testCenterIcon_udfps_refactor_on() {
-        mSetFlagsRule.enableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+    fun testCenterIcon() {
         val cs = ConstraintSet()
         underTest.centerIcon(Point(5, 6), 1F, cs)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarInitializerStoreTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarInitializerStoreTest.kt
new file mode 100644
index 0000000..0d1d37a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarInitializerStoreTest.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.statusbar.core
+
+import android.platform.test.annotations.EnableFlags
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.unconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class MultiDisplayStatusBarInitializerStoreTest : SysuiTestCase() {
+
+    private val kosmos =
+        testKosmos().also {
+            // Using unconfinedTestDispatcher to avoid having to call `runCurrent` in the tests.
+            it.testDispatcher = it.unconfinedTestDispatcher
+        }
+    private val testScope = kosmos.testScope
+    private val fakeDisplayRepository = kosmos.displayRepository
+    private val store = kosmos.multiDisplayStatusBarInitializerStore
+
+    @Before
+    fun start() {
+        store.start()
+    }
+
+    @Before
+    fun addDisplays() = runBlocking {
+        fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY_ID)
+        fakeDisplayRepository.addDisplay(NON_DEFAULT_DISPLAY_ID)
+    }
+
+    @Test
+    fun forDisplay_defaultDisplay_multipleCalls_returnsSameInstance() =
+        testScope.runTest {
+            val controller = store.defaultDisplay
+
+            assertThat(store.defaultDisplay).isSameInstanceAs(controller)
+        }
+
+    @Test
+    fun forDisplay_nonDefaultDisplay_multipleCalls_returnsSameInstance() =
+        testScope.runTest {
+            val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID)
+
+            assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isSameInstanceAs(controller)
+        }
+
+    @Test
+    fun forDisplay_nonDefaultDisplay_afterDisplayRemoved_returnsNewInstance() =
+        testScope.runTest {
+            val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID)
+
+            fakeDisplayRepository.removeDisplay(NON_DEFAULT_DISPLAY_ID)
+            fakeDisplayRepository.addDisplay(NON_DEFAULT_DISPLAY_ID)
+
+            assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isNotSameInstanceAs(controller)
+        }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun forDisplay_nonExistingDisplayId_throws() =
+        testScope.runTest { store.forDisplay(NON_EXISTING_DISPLAY_ID) }
+
+    companion object {
+        private const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
+        private const val NON_DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1
+        private const val NON_EXISTING_DISPLAY_ID = Display.DEFAULT_DISPLAY + 2
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 376873d..5d8a8fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.statusbar.BatteryStatusChip
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
@@ -63,6 +64,7 @@
     @Mock private lateinit var systemEventCoordinator: SystemEventCoordinator
 
     @Mock private lateinit var statusBarWindowController: StatusBarWindowController
+    @Mock private lateinit var statusBarWindowControllerStore: StatusBarWindowControllerStore
 
     @Mock private lateinit var statusBarContentInsetProvider: StatusBarContentInsetsProvider
 
@@ -82,12 +84,14 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
 
+        whenever(statusBarWindowControllerStore.defaultDisplay)
+            .thenReturn(statusBarWindowController)
         systemClock = FakeSystemClock()
         chipAnimationController =
             SystemEventChipAnimationController(
                 mContext,
-                statusBarWindowController,
-                statusBarContentInsetProvider
+                statusBarWindowControllerStore,
+                statusBarContentInsetProvider,
             )
 
         // StatusBarContentInsetProvider is mocked. Ensure that it returns some mocked values.
@@ -660,7 +664,7 @@
             SystemStatusAnimationSchedulerImpl(
                 systemEventCoordinator,
                 chipAnimationController,
-                statusBarWindowController,
+                statusBarWindowControllerStore,
                 dumpManager,
                 systemClock,
                 CoroutineScope(StandardTestDispatcher(testScope.testScheduler)),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 15ea811..40b8cdaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -170,6 +170,7 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.core.StatusBarInitializerImpl;
 import com.android.systemui.statusbar.core.StatusBarOrchestrator;
+import com.android.systemui.statusbar.core.StatusBarSimpleFragment;
 import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -194,6 +195,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.util.FakeEventLog;
 import com.android.systemui.util.WallpaperController;
@@ -292,6 +294,7 @@
     @Mock private KeyguardBypassController mKeyguardBypassController;
     @Mock private AutoHideController mAutoHideController;
     @Mock private StatusBarWindowController mStatusBarWindowController;
+    @Mock private StatusBarWindowControllerStore mStatusBarWindowControllerStore;
     @Mock private Provider<CollapsedStatusBarFragment> mCollapsedStatusBarFragmentProvider;
     @Mock private StatusBarWindowStateController mStatusBarWindowStateController;
     @Mock private Bubbles mBubbles;
@@ -383,6 +386,9 @@
 
         when(mBubbles.canShowBubbleNotification()).thenReturn(true);
 
+        when(mStatusBarWindowControllerStore.getDefaultDisplay())
+                .thenReturn(mStatusBarWindowController);
+
         mVisualInterruptionDecisionProvider =
                 VisualInterruptionDecisionProviderTestUtil.INSTANCE.createProviderByFlag(
                         mAmbientDisplayConfiguration,
@@ -465,7 +471,7 @@
                     mKeyguardStateController,
                     mStatusBarStateController,
                     mStatusBarKeyguardViewManager,
-                    mStatusBarWindowController,
+                    mStatusBarWindowControllerStore,
                     mDeviceProvisionedController,
                     mNotificationShadeWindowController,
                     0,
@@ -507,10 +513,11 @@
                 mLightBarController,
                 mAutoHideController,
                 new StatusBarInitializerImpl(
-                        mStatusBarWindowController,
+                        mContext.getDisplayId(),
+                        mStatusBarWindowControllerStore,
                         mCollapsedStatusBarFragmentProvider,
                         emptySet()),
-                mStatusBarWindowController,
+                mStatusBarWindowControllerStore,
                 mStatusBarWindowStateController,
                 new FakeStatusBarModeRepository(),
                 mKeyguardUpdateMonitor,
@@ -1149,6 +1156,7 @@
     }
 
     @Test
+    @DisableFlags(StatusBarSimpleFragment.FLAG_NAME)
     public void bubbleBarVisibility() {
         createCentralSurfaces();
         mCentralSurfaces.onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index 68df748..ee79ca0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -57,10 +58,15 @@
         get() = view.requireViewById(R.id.system_icons)
 
     private val windowController = mock<StatusBarWindowController>()
+    private val windowControllerStore = mock<StatusBarWindowControllerStore>()
 
     @Before
     fun setUp() {
-        mDependency.injectTestDependency(StatusBarWindowController::class.java, windowController)
+        whenever(windowControllerStore.defaultDisplay).thenReturn(windowController)
+        mDependency.injectTestDependency(
+            StatusBarWindowControllerStore::class.java,
+            windowControllerStore,
+        )
         context.ensureTestableResources()
         view = spy(createStatusBarView())
         whenever(view.rootWindowInsets).thenReturn(emptyWindowInsets())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 9cfb0bb..5996ef1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -46,6 +46,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
@@ -2470,6 +2471,37 @@
         verify(stackView, never()).showOverflow(anyBoolean());
     }
 
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void testEventLogging_bubbleBar_addBubble() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        mEntryListener.onEntryAdded(mRow);
+
+        verify(mBubbleLogger).log(argThat(b -> b.getKey().equals(mRow.getKey())),
+                eq(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_POSTED));
+    }
+
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void testEventLogging_bubbleBar_updateBubble() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        mEntryListener.onEntryAdded(mRow);
+        // Mark the notification as updated
+        NotificationEntryHelper.modifyRanking(mRow).setTextChanged(true).build();
+        mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true);
+
+        verify(mBubbleLogger).log(argThat(b -> b.getKey().equals(mRow.getKey())),
+                eq(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_UPDATED));
+    }
+
     /** Creates a bubble using the userId and package. */
     private Bubble createBubble(int userId, String pkg) {
         final UserHandle userHandle = new UserHandle(userId);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/wmshell/OWNERS
new file mode 100644
index 0000000..eae8629
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/OWNERS
@@ -0,0 +1,5 @@
+# Bubbles team
+madym@google.com
+atsjenk@google.com
+liranb@google.com
+mpodolian@google.com
\ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index fcc83b3..78ea700 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -53,6 +53,10 @@
     private val displayAdditionEventFlow = MutableSharedFlow<Display?>(replay = 0)
     private val displayRemovalEventFlow = MutableSharedFlow<Int>(replay = 0)
 
+    suspend fun addDisplay(displayId: Int, type: Int = Display.TYPE_EXTERNAL) {
+        addDisplay(display(type, id = displayId))
+    }
+
     suspend fun addDisplay(display: Display) {
         flow.value += display
         displayAdditionEventFlow.emit(display)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
index ddcc6d6..b9f0c9a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
@@ -37,7 +37,7 @@
 import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.policy.deviceProvisionedController
-import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.util.mockito.mock
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
@@ -67,7 +67,7 @@
             mock<KeyguardStateController>(),
             statusBarStateController,
             statusBarKeyguardViewManager,
-            mock<StatusBarWindowController>(),
+            mock<StatusBarWindowControllerStore>(),
             deviceProvisionedController,
             mock<NotificationShadeWindowController>(),
             0,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
new file mode 100644
index 0000000..73ed228
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.core
+
+import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
+import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
+
+class FakeStatusBarInitializerFactory(
+    private val statusBarViewController: PhoneStatusBarViewController,
+    private val statusBarTransitions: PhoneStatusBarTransitions,
+) : StatusBarInitializer.Factory {
+
+    override fun create(displayId: Int): StatusBarInitializer =
+        FakeStatusBarInitializer(statusBarViewController, statusBarTransitions)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
index d103200..7ad715b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.statusbar.core
 
+import com.android.systemui.display.data.repository.displayRepository
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.statusbar.phone.phoneStatusBarTransitions
 import com.android.systemui.statusbar.phone.phoneStatusBarViewController
 
@@ -26,3 +28,20 @@
     }
 
 var Kosmos.statusBarInitializer by Kosmos.Fixture { fakeStatusBarInitializer }
+
+val Kosmos.fakeStatusBarInitializerFactory by
+    Kosmos.Fixture {
+        FakeStatusBarInitializerFactory(phoneStatusBarViewController, phoneStatusBarTransitions)
+    }
+
+var Kosmos.statusBarInitializerFactory: StatusBarInitializer.Factory by
+    Kosmos.Fixture { fakeStatusBarInitializerFactory }
+
+val Kosmos.multiDisplayStatusBarInitializerStore by
+    Kosmos.Fixture {
+        MultiDisplayStatusBarInitializerStore(
+            applicationCoroutineScope,
+            fakeStatusBarInitializerFactory,
+            displayRepository,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
index c53e44d..54de293 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
@@ -28,7 +28,7 @@
 import com.android.systemui.statusbar.mockNotificationRemoteInputManager
 import com.android.systemui.statusbar.phone.mockAutoHideController
 import com.android.systemui.statusbar.window.data.repository.statusBarWindowStateRepositoryStore
-import com.android.systemui.statusbar.window.fakeStatusBarWindowController
+import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore
 import com.android.wm.shell.bubbles.bubblesOptional
 
 val Kosmos.statusBarOrchestrator by
@@ -36,8 +36,8 @@
         StatusBarOrchestrator(
             applicationCoroutineScope,
             fakeStatusBarInitializer,
-            fakeStatusBarWindowController,
             fakeStatusBarModeRepository,
+            fakeStatusBarWindowControllerStore,
             mockDemoModeController,
             mockPluginDependencyProvider,
             mockAutoHideController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerStore.kt
index d19e322..35f95b6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerStore.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerStore.kt
@@ -22,10 +22,10 @@
 
     private val perDisplayControllers = mutableMapOf<Int, FakeStatusBarWindowController>()
 
-    override val defaultDisplay
+    override val defaultDisplay: FakeStatusBarWindowController
         get() = forDisplay(Display.DEFAULT_DISPLAY)
 
-    override fun forDisplay(displayId: Int): StatusBarWindowController {
+    override fun forDisplay(displayId: Int): FakeStatusBarWindowController {
         return perDisplayControllers.computeIfAbsent(displayId) { FakeStatusBarWindowController() }
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
index 6c6f243..78caf93 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
@@ -20,7 +20,8 @@
 
 val Kosmos.fakeStatusBarWindowController by Kosmos.Fixture { FakeStatusBarWindowController() }
 
-var Kosmos.statusBarWindowController by Kosmos.Fixture { fakeStatusBarWindowController }
+var Kosmos.statusBarWindowController: StatusBarWindowController by
+    Kosmos.Fixture { fakeStatusBarWindowController }
 
 val Kosmos.fakeStatusBarWindowControllerStore by
     Kosmos.Fixture { FakeStatusBarWindowControllerStore() }
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 8896d77..bfa801f 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -8,7 +8,7 @@
 
     // OWNER: g/ravenwood
     // Bug component: 25698
-    default_team: "trendy_team_framework_backstage_power",
+    default_team: "trendy_team_ravenwood",
 }
 
 filegroup {
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 908e590..5894476 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -335,7 +335,11 @@
         }
         android.os.Process.reset$ravenwood();
 
-        ResourcesManager.setInstance(null); // Better structure needed.
+        try {
+            ResourcesManager.setInstance(null); // Better structure needed.
+        } catch (Exception e) {
+            // AOSP-CHANGE: AOSP doesn't support resources yet.
+        }
 
         if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
             maybeThrowPendingUncaughtException(true);
diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java
index 57c643b..a7aab49 100644
--- a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java
+++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java
@@ -160,11 +160,13 @@
             HardwareBuffer snapshotBuffer = null;
             int colorSpaceId = 0;
 
+            TaskSnapshot snapshot = null;
             // Skip taking TaskSnapshot when bitmap is provided.
             if (!imageContextRequestExtras.containsKey(ContentSuggestionsManager.EXTRA_BITMAP)) {
                 // Can block, so call before acquiring the lock.
-                TaskSnapshot snapshot =
-                        mActivityTaskManagerInternal.getTaskSnapshotBlocking(taskId, false);
+                snapshot = mActivityTaskManagerInternal.getTaskSnapshotBlocking(
+                        taskId, false /* isLowResolution */,
+                        TaskSnapshot.REFERENCE_CONTENT_SUGGESTION);
                 if (snapshot != null) {
                     snapshotBuffer = snapshot.getHardwareBuffer();
                     ColorSpace colorSpace = snapshot.getColorSpace();
@@ -185,6 +187,9 @@
                     }
                 }
             }
+            if (snapshot != null) {
+                snapshot.removeReference(TaskSnapshot.REFERENCE_CONTENT_SUGGESTION);
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 05d07ae..485bf31 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -1523,7 +1523,7 @@
                 @Override
                 public void onApexStaged(ApexStagedEvent event) throws RemoteException {
                     Slog.d(TAG, "A new APEX has been staged for update. There are currently "
-                            + event.stagedApexModuleNames.length + " APEX(s) staged for update. "
+                            + event.stagedApexInfos.length + " APEX(s) staged for update. "
                             + "Scheduling measurement...");
                     UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext,
                             BinaryTransparencyService.this);
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 12e8c57..947f6b7 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -48,7 +48,6 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
-import android.net.vcn.Flags;
 import android.net.vcn.IVcnManagementService;
 import android.net.vcn.IVcnStatusCallback;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
@@ -447,22 +446,16 @@
         }
 
         final UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
+        final UserManager userManager = mContext.getSystemService(UserManager.class);
 
-        if (Flags.enforceMainUser()) {
-            final UserManager userManager = mContext.getSystemService(UserManager.class);
-
-            Binder.withCleanCallingIdentity(
-                    () -> {
-                        if (!Objects.equals(userManager.getMainUser(), userHandle)) {
-                            throw new SecurityException(
-                                    "VcnManagementService can only be used by callers running as"
-                                            + " the main user");
-                        }
-                    });
-        } else if (!userHandle.isSystem()) {
-            throw new SecurityException(
-                    "VcnManagementService can only be used by callers running as the primary user");
-        }
+        Binder.withCleanCallingIdentity(
+                () -> {
+                    if (!Objects.equals(userManager.getMainUser(), userHandle)) {
+                        throw new SecurityException(
+                                "VcnManagementService can only be used by callers running as"
+                                        + " the main user");
+                    }
+                });
     }
 
     private void enforceCallingUserAndCarrierPrivilege(
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 60dbf3f..dbdc614 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -30,6 +30,7 @@
 import android.bluetooth.BluetoothProfile;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
+import android.content.AttributionSource;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -306,14 +307,13 @@
      * @param on true to enable speakerphone
      * @param eventSource for logging purposes
      */
-    /*package*/ void setSpeakerphoneOn(
-            IBinder cb, int uid, boolean on, boolean isPrivileged, String eventSource) {
-
+    /*package*/ void setSpeakerphoneOn(IBinder cb, @NonNull AttributionSource attributionSource,
+            boolean on, boolean isPrivileged, String eventSource) {
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "setSpeakerphoneOn, on: " + on + " uid: " + uid);
+            Log.v(TAG, "setSpeakerphoneOn, on: " + on + " uid: " + attributionSource.getUid());
         }
-        postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
-                cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
+        postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(cb, attributionSource,
+                new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
                 on, BtHelper.SCO_MODE_UNDEFINED, eventSource, isPrivileged));
     }
 
@@ -332,16 +332,18 @@
      * @param eventSource for logging purposes
      * @return false if there is no device and no communication client
      */
-    /*package*/ boolean setCommunicationDevice(IBinder cb, int uid, AudioDeviceInfo device,
-                                               boolean isPrivileged, String eventSource) {
-
+    /*package*/ boolean setCommunicationDevice(IBinder cb,
+            @NonNull AttributionSource attributionSource, AudioDeviceInfo device,
+            boolean isPrivileged, String eventSource) {
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "setCommunicationDevice, device: " + device + ", uid: " + uid);
+            Log.v(TAG, "setCommunicationDevice, device: " + device
+                    + ", uid: " + attributionSource.getUid());
         }
 
         if (device == null) {
             synchronized (mDeviceStateLock) {
-                CommunicationRouteClient client = getCommunicationRouteClientForUid(uid);
+                CommunicationRouteClient client =
+                        getCommunicationRouteClientForUid(attributionSource.getUid());
                 if (client == null) {
                     return false;
                 }
@@ -351,7 +353,8 @@
             mCommunicationDeviceUpdateCount++;
             AudioDeviceAttributes deviceAttr =
                     (device != null) ? new AudioDeviceAttributes(device) : null;
-            CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, uid, deviceAttr,
+            CommunicationDeviceInfo deviceInfo =
+                    new CommunicationDeviceInfo(cb, attributionSource, deviceAttr,
                     device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, isPrivileged);
             postSetCommunicationDeviceForClient(deviceInfo);
         }
@@ -369,7 +372,8 @@
             Log.v(TAG, "onSetCommunicationDeviceForClient: " + deviceInfo);
         }
         if (!deviceInfo.mOn) {
-            CommunicationRouteClient client = getCommunicationRouteClientForUid(deviceInfo.mUid);
+            CommunicationRouteClient client =
+                    getCommunicationRouteClientForUid(deviceInfo.mAttributionSource.getUid());
             if (client == null || (deviceInfo.mDevice != null
                     && !deviceInfo.mDevice.equals(client.getDevice()))) {
                 return;
@@ -377,53 +381,60 @@
         }
 
         AudioDeviceAttributes device = deviceInfo.mOn ? deviceInfo.mDevice : null;
-        setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mUid, device,
-                deviceInfo.mScoAudioMode, deviceInfo.mIsPrivileged, deviceInfo.mEventSource);
+        setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mAttributionSource,
+                device, deviceInfo.mScoAudioMode, deviceInfo.mIsPrivileged,
+                deviceInfo.mEventSource);
     }
 
     /**
      * Indicates if a Bluetooth SCO activation request owner is controlling
      * the SCO audio state itself or not.
-     * @param uid the UID of the SOC request owner app
+     * @param attributionSource the AttributionSource of the SCO request owner app
      * @return true if we should control SCO audio state, false otherwise
      */
-    private boolean shouldStartScoForUid(int uid) {
+    private boolean shouldStartScoForAttributionSource(AttributionSource attributionSource) {
+        if (attributionSource == null) {
+            return true;
+        }
+        int uid = attributionSource.getUid();
         return !(UserHandle.isSameApp(uid, Process.BLUETOOTH_UID)
                 || UserHandle.isSameApp(uid, Process.PHONE_UID)
-                || UserHandle.isSameApp(uid, Process.SYSTEM_UID));
+                || (UserHandle.isSameApp(uid, Process.SYSTEM_UID)
+                    && "com.android.server.telecom".equals(attributionSource.getPackageName())));
     }
 
     @GuardedBy("mDeviceStateLock")
     /*package*/ void setCommunicationRouteForClient(
-                            IBinder cb, int uid, AudioDeviceAttributes device,
-                            int scoAudioMode, boolean isPrivileged, String eventSource) {
-
+            IBinder cb, @NonNull AttributionSource attributionSource, AudioDeviceAttributes device,
+            int scoAudioMode, boolean isPrivileged, String eventSource) {
         if (AudioService.DEBUG_COMM_RTE) {
             Log.v(TAG, "setCommunicationRouteForClient: device: " + device
                     + ", eventSource: " + eventSource);
         }
         AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
-                                        "setCommunicationRouteForClient for uid: " + uid
+                                        "setCommunicationRouteForClient for uid: "
+                                        + attributionSource.getUid()
                                         + " device: " + device + " isPrivileged: " + isPrivileged
                                         + " from API: " + eventSource)).printLog(TAG));
 
-        final int previousBtScoRequesterUid = bluetoothScoRequestOwnerUid();
+        final AttributionSource previousBtScoRequesterAS =
+                bluetoothScoRequestOwnerAttributionSource();
         CommunicationRouteClient client;
 
         // Save previous client route in case of failure to start BT SCO audio
         AudioDeviceAttributes prevClientDevice = null;
         boolean prevPrivileged = false;
-        client = getCommunicationRouteClientForUid(uid);
+        client = getCommunicationRouteClientForUid(attributionSource.getUid());
         if (client != null) {
             prevClientDevice = client.getDevice();
             prevPrivileged = client.isPrivileged();
         }
 
         if (device != null) {
-            client = addCommunicationRouteClient(cb, uid, device, isPrivileged);
+            client = addCommunicationRouteClient(cb, attributionSource, device, isPrivileged);
             if (client == null) {
                 Log.w(TAG, "setCommunicationRouteForClient: could not add client for uid: "
-                        + uid + " and device: " + device);
+                        + attributionSource.getUid() + " and device: " + device);
             }
         } else {
             client = removeCommunicationRouteClient(cb, true);
@@ -431,22 +442,23 @@
         if (client == null) {
             return;
         }
-        final int btScoRequesterUid = bluetoothScoRequestOwnerUid();
-        final boolean isBtScoRequested = btScoRequesterUid != -1;
-        final boolean wasBtScoRequested = previousBtScoRequesterUid != -1;
+        final AttributionSource btScoRequesterAS = bluetoothScoRequestOwnerAttributionSource();
+        final boolean isBtScoRequested = btScoRequesterAS != null;
+        final boolean wasBtScoRequested = previousBtScoRequesterAS != null;
 
         if (mScoManagedByAudio) {
             if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive()
                     || !mBtHelper.isBluetoothScoRequestedInternally())) {
                 boolean scoStarted = false;
-                if (shouldStartScoForUid(btScoRequesterUid)) {
+                if (shouldStartScoForAttributionSource(btScoRequesterAS)) {
                     scoStarted = mBtHelper.startBluetoothSco(scoAudioMode, eventSource);
                     if (!scoStarted) {
                         Log.w(TAG, "setCommunicationRouteForClient: "
-                                + "failure to start BT SCO for uid: " + uid);
+                                + "failure to start BT SCO for uid: " + attributionSource.getUid());
                         // clean up or restore previous client selection
                         if (prevClientDevice != null) {
-                            addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged);
+                            addCommunicationRouteClient(cb, attributionSource,
+                                    prevClientDevice, prevPrivileged);
                         } else {
                             removeCommunicationRouteClient(cb, true);
                         }
@@ -459,7 +471,7 @@
                     setBluetoothScoOn(true, "setCommunicationRouteForClient");
                 }
             } else if (!isBtScoRequested && wasBtScoRequested) {
-                if (shouldStartScoForUid(previousBtScoRequesterUid)) {
+                if (shouldStartScoForAttributionSource(previousBtScoRequesterAS)) {
                     mBtHelper.stopBluetoothSco(eventSource);
                 }
                 setBluetoothScoOn(false, "setCommunicationRouteForClient");
@@ -469,10 +481,11 @@
                     || !mBtHelper.isBluetoothScoRequestedInternally())) {
                 if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) {
                     Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for uid: "
-                            + uid);
+                            + attributionSource.getUid());
                     // clean up or restore previous client selection
                     if (prevClientDevice != null) {
-                        addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged);
+                        addCommunicationRouteClient(cb, attributionSource,
+                                prevClientDevice, prevPrivileged);
                     } else {
                         removeCommunicationRouteClient(cb, true);
                     }
@@ -583,7 +596,7 @@
                 // Cancelling the route for this client will remove it from the stack and update
                 // the communication route.
                 CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(
-                        crc.getBinder(), crc.getUid(), device, false,
+                        crc.getBinder(), crc.getAttributionSource(), device, false,
                         BtHelper.SCO_MODE_UNDEFINED, "onCheckCommunicationDeviceRemoval",
                         crc.isPrivileged());
                 postSetCommunicationDeviceForClient(deviceInfo);
@@ -619,12 +632,11 @@
     @GuardedBy("mDeviceStateLock")
     /*package*/ void updateCommunicationRouteClientState(
                             CommunicationRouteClient client, boolean wasActive) {
-        int btScoRequesterUid = bluetoothScoRequestOwnerUid();
         client.setPlaybackActive(mAudioService.isPlaybackActiveForUid(client.getUid()));
         client.setRecordingActive(mAudioService.isRecordingActiveForUid(client.getUid()));
         if (wasActive != client.isActive()) {
-            postUpdateCommunicationRouteClient(
-                    btScoRequesterUid, "updateCommunicationRouteClientState");
+            postUpdateCommunicationRouteClient(bluetoothScoRequestOwnerAttributionSource(),
+                    "updateCommunicationRouteClientState");
         }
     }
 
@@ -810,21 +822,26 @@
     }
 
     /**
-     * Helper method on top of isBluetoothScoRequested() returning the UID of the
-     * BT SCO route request owner of -1 if SCO is not requested.
-     * @return the UID of the BT SCO route request owner of -1 if SCO is not requested.
+     * Helper method on top of isBluetoothScoRequested() returning the Attribution Source of the
+     * BT SCO route request owner or null if SCO is not requested.
+     * @return the AttributionSource of the BT SCO route request owner of null.
      */
     @GuardedBy("mDeviceStateLock")
-    /*package*/ int bluetoothScoRequestOwnerUid() {
+    /*package*/ @Nullable AttributionSource bluetoothScoRequestOwnerAttributionSource() {
         if (!isBluetoothScoRequested()) {
-            return -1;
+            return null;
         }
         CommunicationRouteClient crc = topCommunicationRouteClient();
         if (crc == null) {
-            return -1;
+            return null;
         }
-        return crc.getUid();
+        return crc.getAttributionSource();
     }
+
+    private static int safeUidFromAttributionSource(AttributionSource attributionSource) {
+        return (attributionSource != null) ? attributionSource.getUid() : -1;
+    }
+
     /**
      * Helper method on top of isDeviceRequestedForCommunication() indicating if
      * Bluetooth LE Audio communication device is currently requested or not.
@@ -1222,14 +1239,15 @@
     @GuardedBy("mDeviceStateLock")
     /*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
         synchronized (mBluetoothAudioStateLock) {
-            int btScoRequesterUId = bluetoothScoRequestOwnerUid();
+            AttributionSource btScoRequesterAS = bluetoothScoRequestOwnerAttributionSource();
             Log.i(TAG, "setBluetoothScoOn: " + on + ", mBluetoothScoOn: "
-                    + mBluetoothScoOn + ", btScoRequesterUId: " + btScoRequesterUId
+                    + mBluetoothScoOn + ", btScoRequesterUId: "
+                    + safeUidFromAttributionSource(btScoRequesterAS)
                     + ", from: " + eventSource);
             mBluetoothScoOn = on;
             updateAudioHalBluetoothState();
             if (!mScoManagedByAudio) {
-                postUpdateCommunicationRouteClient(btScoRequesterUId, eventSource);
+                postUpdateCommunicationRouteClient(btScoRequesterAS, eventSource);
             }
         }
     }
@@ -1332,25 +1350,26 @@
         sendLMsgNoDelay(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, info);
     }
 
-    /*package*/ void startBluetoothScoForClient(IBinder cb, int uid, int scoAudioMode,
-                                                boolean isPrivileged, @NonNull String eventSource) {
-
+    /*package*/ void startBluetoothScoForClient(IBinder cb,
+            @NonNull AttributionSource attributionSource, int scoAudioMode, boolean isPrivileged,
+            @NonNull String eventSource) {
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "startBluetoothScoForClient, uid: " + uid);
+            Log.v(TAG, "startBluetoothScoForClient, uid: " + attributionSource.getUid());
         }
-        postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
-                cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+        postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(cb, attributionSource,
+                new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
                 true, scoAudioMode, eventSource, isPrivileged));
     }
 
-    /*package*/ void stopBluetoothScoForClient(
-                        IBinder cb, int uid, boolean isPrivileged, @NonNull String eventSource) {
-
+    /*package*/ void stopBluetoothScoForClient(IBinder cb,
+            @NonNull AttributionSource attributionSource, boolean isPrivileged,
+            @NonNull String eventSource) {
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "stopBluetoothScoForClient, uid: " + uid);
+            Log.v(TAG, "stopBluetoothScoForClient, uid: " + attributionSource.getUid());
         }
         postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
-                cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+                cb, attributionSource, new AudioDeviceAttributes(
+                                              AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
                 false, BtHelper.SCO_MODE_UNDEFINED, eventSource, isPrivileged));
     }
 
@@ -1566,10 +1585,21 @@
         sendLMsgNoDelay(MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED, SENDMSG_QUEUE, client);
     }
 
+    private static final class UpdateCommRouteClientInfo {
+        @NonNull public final AttributionSource attributionSource;
+        @NonNull public final String eventSource;
+
+        UpdateCommRouteClientInfo(@NonNull AttributionSource attributionSource,
+                @NonNull String eventSource) {
+            this.attributionSource = attributionSource;
+            this.eventSource = eventSource;
+        }
+    }
+
     /*package*/ void postUpdateCommunicationRouteClient(
-            int btScoRequesterUid, String eventSource) {
-        sendILMsgNoDelay(MSG_IL_UPDATE_COMMUNICATION_ROUTE_CLIENT, SENDMSG_QUEUE,
-                btScoRequesterUid, eventSource);
+            AttributionSource attributionSource, String eventSource) {
+        sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT, SENDMSG_QUEUE,
+            new UpdateCommRouteClientInfo(attributionSource, eventSource));
     }
 
     /*package*/ void postSetCommunicationDeviceForClient(CommunicationDeviceInfo info) {
@@ -1600,18 +1630,18 @@
 
     /*package*/ static final class CommunicationDeviceInfo {
         final @NonNull IBinder mCb; // Identifies the requesting client for death handler
-        final int mUid; // Requester UID
+        final @NonNull AttributionSource mAttributionSource; // Requester attribution source
         final @Nullable AudioDeviceAttributes mDevice; // Device being set or reset.
         final boolean mOn; // true if setting, false if resetting
         final int mScoAudioMode; // only used for SCO: requested audio mode
         final boolean mIsPrivileged; // true if the client app has MODIFY_PHONE_STATE permission
         final @NonNull String mEventSource; // caller identifier for logging
 
-        CommunicationDeviceInfo(@NonNull IBinder cb, int uid,
+        CommunicationDeviceInfo(@NonNull IBinder cb, @NonNull AttributionSource attributionSource,
                 @Nullable AudioDeviceAttributes device, boolean on, int scoAudioMode,
                 @NonNull String eventSource, boolean isPrivileged) {
             mCb = cb;
-            mUid = uid;
+            mAttributionSource = attributionSource;
             mDevice = device;
             mOn = on;
             mScoAudioMode = scoAudioMode;
@@ -1633,19 +1663,19 @@
             }
 
             return mCb.equals(((CommunicationDeviceInfo) o).mCb)
-                    && mUid == ((CommunicationDeviceInfo) o).mUid;
+                    && mAttributionSource.equals(((CommunicationDeviceInfo) o).mAttributionSource);
         }
 
         @Override
         public int hashCode() {
             // only hashing on the fields used in equals()
-            return Objects.hash(mCb.hashCode(), mUid);
+            return Objects.hash(mCb.hashCode(), mAttributionSource);
         }
 
         @Override
         public String toString() {
             return "CommunicationDeviceInfo mCb=" + mCb.toString()
-                    + " mUid=" + mUid
+                    + " mAttributionSource=" + mAttributionSource.toString()
                     + " mDevice=[" + (mDevice != null ? mDevice.toString() : "null") + "]"
                     + " mOn=" + mOn
                     + " mScoAudioMode=" + mScoAudioMode
@@ -1929,7 +1959,8 @@
                                         || btInfo.mProfile == BluetoothProfile.HEARING_AID
                                         || (mScoManagedByAudio
                                             && btInfo.mProfile == BluetoothProfile.HEADSET)) {
-                                    onUpdateCommunicationRouteClient(bluetoothScoRequestOwnerUid(),
+                                    onUpdateCommunicationRouteClient(
+                                            bluetoothScoRequestOwnerAttributionSource(),
                                             "setBluetoothActiveDevice");
                                 }
                             }
@@ -1998,11 +2029,11 @@
                 case MSG_IL_SET_MODE_OWNER:
                     synchronized (mSetModeLock) {
                         synchronized (mDeviceStateLock) {
-                            int btScoRequesterUid = bluetoothScoRequestOwnerUid();
                             mAudioModeOwner = (AudioModeInfo) msg.obj;
                             if (mAudioModeOwner.mMode != AudioSystem.MODE_RINGTONE) {
                                 onUpdateCommunicationRouteClient(
-                                        btScoRequesterUid, "setNewModeOwner");
+                                        bluetoothScoRequestOwnerAttributionSource(),
+                                        "setNewModeOwner");
                             }
                         }
                     }
@@ -2029,10 +2060,12 @@
                     }
                     break;
 
-                case MSG_IL_UPDATE_COMMUNICATION_ROUTE_CLIENT:
+                case MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT:
                     synchronized (mSetModeLock) {
                         synchronized (mDeviceStateLock) {
-                            onUpdateCommunicationRouteClient(msg.arg1, (String) msg.obj);
+                            UpdateCommRouteClientInfo info = (UpdateCommRouteClientInfo) msg.obj;
+                            onUpdateCommunicationRouteClient(
+                                    info.attributionSource, info.eventSource);
                         }
                     }
                     break;
@@ -2182,7 +2215,7 @@
     private static final int MSG_REPORT_NEW_ROUTES_A2DP = 36;
 
     private static final int MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT = 42;
-    private static final int MSG_IL_UPDATE_COMMUNICATION_ROUTE_CLIENT = 43;
+    private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT = 43;
 
     private static final int MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT = 45;
     //
@@ -2394,20 +2427,20 @@
 
     private class CommunicationRouteClient implements IBinder.DeathRecipient {
         private final IBinder mCb;
-        private final int mUid;
+        @NonNull private final AttributionSource mAttributionSource;
         private final boolean mIsPrivileged;
         private AudioDeviceAttributes mDevice;
         private boolean mPlaybackActive;
         private boolean mRecordingActive;
 
-        CommunicationRouteClient(IBinder cb, int uid, AudioDeviceAttributes device,
-                                 boolean isPrivileged) {
+        CommunicationRouteClient(IBinder cb, @NonNull AttributionSource attributionSource,
+                AudioDeviceAttributes device, boolean isPrivileged) {
             mCb = cb;
-            mUid = uid;
+            mAttributionSource = attributionSource;
             mDevice = device;
             mIsPrivileged = isPrivileged;
-            mPlaybackActive = mAudioService.isPlaybackActiveForUid(uid);
-            mRecordingActive = mAudioService.isRecordingActiveForUid(uid);
+            mPlaybackActive = mAudioService.isPlaybackActiveForUid(attributionSource.getUid());
+            mRecordingActive = mAudioService.isRecordingActiveForUid(attributionSource.getUid());
         }
 
         public boolean registerDeathRecipient() {
@@ -2438,8 +2471,12 @@
             return mCb;
         }
 
+        @NonNull AttributionSource getAttributionSource() {
+            return mAttributionSource;
+        }
+
         int getUid() {
-            return mUid;
+            return mAttributionSource.getUid();
         }
 
         boolean isPrivileged() {
@@ -2464,7 +2501,7 @@
 
         @Override
         public String toString() {
-            return "[CommunicationRouteClient: mUid: " + mUid
+            return "[CommunicationRouteClient: mAttributionSource: " + mAttributionSource
                     + " mDevice: " + mDevice.toString()
                     + " mIsPrivileged: " + mIsPrivileged
                     + " mPlaybackActive: " + mPlaybackActive
@@ -2479,8 +2516,8 @@
             return;
         }
         Log.w(TAG, "Communication client died");
-        setCommunicationRouteForClient(client.getBinder(), client.getUid(), null,
-                BtHelper.SCO_MODE_UNDEFINED, client.isPrivileged(),
+        setCommunicationRouteForClient(client.getBinder(), client.getAttributionSource(),
+                null, BtHelper.SCO_MODE_UNDEFINED, client.isPrivileged(),
                 "onCommunicationRouteClientDied");
     }
 
@@ -2561,21 +2598,22 @@
     // @GuardedBy("mSetModeLock")
     @GuardedBy("mDeviceStateLock")
     private void onUpdateCommunicationRouteClient(
-            int previousBtScoRequesterUid, String eventSource) {
+            @Nullable AttributionSource previousBtScoRequesterAS, String eventSource) {
         CommunicationRouteClient crc = topCommunicationRouteClient();
         if (AudioService.DEBUG_COMM_RTE) {
             Log.v(TAG, "onUpdateCommunicationRouteClient, crc: " + crc
-                    + " previousBtScoRequesterUid: " + previousBtScoRequesterUid
+                    + " previous BT SCO Requester UID: "
+                    + safeUidFromAttributionSource(previousBtScoRequesterAS)
                     + " eventSource: " + eventSource);
         }
         if (crc != null) {
-            setCommunicationRouteForClient(crc.getBinder(), crc.getUid(), crc.getDevice(),
-                    BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource);
+            setCommunicationRouteForClient(crc.getBinder(), crc.getAttributionSource(),
+                    crc.getDevice(), BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource);
         } else {
-            boolean wasScoRequested = previousBtScoRequesterUid != -1;
+            boolean wasScoRequested = previousBtScoRequesterAS != null;
             if (!isBluetoothScoRequested() && wasScoRequested) {
                 if (mScoManagedByAudio) {
-                    if (shouldStartScoForUid(previousBtScoRequesterUid)) {
+                    if (shouldStartScoForAttributionSource(previousBtScoRequesterAS)) {
                         mBtHelper.stopBluetoothSco(eventSource);
                     }
                     setBluetoothScoOn(false, eventSource);
@@ -2624,12 +2662,13 @@
     }
 
     @GuardedBy("mDeviceStateLock")
-    private CommunicationRouteClient addCommunicationRouteClient(IBinder cb, int uid,
-                AudioDeviceAttributes device, boolean isPrivileged) {
+    private CommunicationRouteClient addCommunicationRouteClient(
+            IBinder cb, @NonNull AttributionSource attributionSource, AudioDeviceAttributes device,
+            boolean isPrivileged) {
         // always insert new request at first position
         removeCommunicationRouteClient(cb, true);
         CommunicationRouteClient client =
-                new CommunicationRouteClient(cb, uid, device, isPrivileged);
+                new CommunicationRouteClient(cb, attributionSource, device, isPrivileged);
         if (client.registerDeathRecipient()) {
             mCommunicationRouteClients.add(0, client);
             if (!client.isActive()) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index fdf7dec..37a2fba 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -69,6 +69,7 @@
 import static com.android.media.audio.Flags.equalScoLeaVcIndexRange;
 import static com.android.media.audio.Flags.replaceStreamBtSco;
 import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
+import static com.android.media.audio.Flags.ringMyCar;
 import static com.android.media.audio.Flags.setStreamVolumeOrder;
 import static com.android.media.audio.Flags.vgsVssSyncMuteOrder;
 import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;
@@ -761,7 +762,7 @@
 
     /** Streams that can be muted by system. Do not resolve to aliases when checking.
      * @see System#MUTE_STREAMS_AFFECTED */
-    private int mMuteAffectedStreams;
+    protected int mMuteAffectedStreams;
 
     /** Streams that can be muted by user. Do not resolve to aliases when checking.
      * @see System#MUTE_STREAMS_AFFECTED */
@@ -1465,7 +1466,8 @@
 
         mPlaybackMonitor =
                 new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM],
-                        device -> onMuteAwaitConnectionTimeout(device));
+                        device -> onMuteAwaitConnectionTimeout(device),
+                        stream -> isStreamMute(stream));
         mPlaybackMonitor.registerPlaybackCallback(mPlaybackActivityMonitor, true);
 
         mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
@@ -4846,6 +4848,8 @@
                 + replaceStreamBtSco());
         pw.println("\tcom.android.media.audio.equalScoLeaVcIndexRange:"
                 + equalScoLeaVcIndexRange());
+        pw.println("\tcom.android.media.audio.ringMyCar:"
+                + ringMyCar());
     }
 
     private void dumpAudioMode(PrintWriter pw) {
@@ -6840,9 +6844,13 @@
      * @see AudioManager#setCommunicationDevice(int)
      * @see AudioManager#clearCommunicationDevice()
      */
-    public boolean setCommunicationDevice(IBinder cb, int portId) {
-        final int uid = Binder.getCallingUid();
-        final int pid = Binder.getCallingPid();
+    public boolean setCommunicationDevice(IBinder cb, int portId,
+            @NonNull AttributionSource attributionSource) {
+        if (attributionSource == null) {
+            return false;
+        }
+        final int uid = attributionSource.getUid();
+        final int pid = attributionSource.getPid();
 
         AudioDeviceInfo device = null;
         if (portId != 0) {
@@ -6892,7 +6900,8 @@
                 == PackageManager.PERMISSION_GRANTED;
         final long ident = Binder.clearCallingIdentity();
         try {
-            return mDeviceBroker.setCommunicationDevice(cb, uid, device, isPrivileged, eventSource);
+            return mDeviceBroker.setCommunicationDevice(
+                    cb, attributionSource, device, isPrivileged, eventSource);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -6934,7 +6943,11 @@
     }
 
     /** @see AudioManager#setSpeakerphoneOn(boolean) */
-    public void setSpeakerphoneOn(IBinder cb, boolean on) {
+    public void setSpeakerphoneOn(IBinder cb, boolean on,
+            @NonNull AttributionSource attributionSource) {
+        if (attributionSource == null) {
+            return;
+        }
         if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) {
             return;
         }
@@ -6942,8 +6955,8 @@
                 == PackageManager.PERMISSION_GRANTED;
 
         // for logging only
-        final int uid = Binder.getCallingUid();
-        final int pid = Binder.getCallingPid();
+        final int uid = attributionSource.getUid();
+        final int pid = attributionSource.getPid();
 
         final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on)
                 .append(") from u/pid:").append(uid).append("/")
@@ -6958,7 +6971,7 @@
 
         final long ident = Binder.clearCallingIdentity();
         try {
-            mDeviceBroker.setSpeakerphoneOn(cb, uid, on, isPrivileged, eventSource);
+            mDeviceBroker.setSpeakerphoneOn(cb, attributionSource, on, isPrivileged, eventSource);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -7062,13 +7075,17 @@
     }
 
     /** @see AudioManager#startBluetoothSco() */
-    public void startBluetoothSco(IBinder cb, int targetSdkVersion) {
+    public void startBluetoothSco(IBinder cb, int targetSdkVersion,
+            @NonNull AttributionSource attributionSource) {
+        if (attributionSource == null) {
+            return;
+        }
         if (!checkAudioSettingsPermission("startBluetoothSco()")) {
             return;
         }
 
-        final int uid = Binder.getCallingUid();
-        final int pid = Binder.getCallingPid();
+        final int uid = attributionSource.getUid();
+        final int pid = attributionSource.getPid();
         final int scoAudioMode =
                 (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ?
                         BtHelper.SCO_MODE_VIRTUAL_CALL : BtHelper.SCO_MODE_UNDEFINED;
@@ -7083,18 +7100,22 @@
                 .set(MediaMetrics.Property.SCO_AUDIO_MODE,
                         BtHelper.scoAudioModeToString(scoAudioMode))
                 .record();
-        startBluetoothScoInt(cb, uid, scoAudioMode, eventSource);
+        startBluetoothScoInt(cb, attributionSource, scoAudioMode, eventSource);
 
     }
 
     /** @see AudioManager#startBluetoothScoVirtualCall() */
-    public void startBluetoothScoVirtualCall(IBinder cb) {
+    public void startBluetoothScoVirtualCall(IBinder cb,
+            @NonNull AttributionSource attributionSource) {
+        if (attributionSource == null) {
+            return;
+        }
         if (!checkAudioSettingsPermission("startBluetoothScoVirtualCall()")) {
             return;
         }
 
-        final int uid = Binder.getCallingUid();
-        final int pid = Binder.getCallingPid();
+        final int uid = attributionSource.getUid();
+        final int pid = attributionSource.getPid();
         final String eventSource = new StringBuilder("startBluetoothScoVirtualCall()")
                 .append(") from u/pid:").append(uid).append("/")
                 .append(pid).toString();
@@ -7106,10 +7127,11 @@
                 .set(MediaMetrics.Property.SCO_AUDIO_MODE,
                         BtHelper.scoAudioModeToString(BtHelper.SCO_MODE_VIRTUAL_CALL))
                 .record();
-        startBluetoothScoInt(cb, uid, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
+        startBluetoothScoInt(cb, attributionSource, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
     }
 
-    void startBluetoothScoInt(IBinder cb, int uid, int scoAudioMode, @NonNull String eventSource) {
+    void startBluetoothScoInt(IBinder cb, AttributionSource attributionSource,
+            int scoAudioMode, @NonNull String eventSource) {
         MediaMetrics.Item mmi = new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH)
                 .set(MediaMetrics.Property.EVENT, "startBluetoothScoInt")
                 .set(MediaMetrics.Property.SCO_AUDIO_MODE,
@@ -7125,7 +7147,7 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             mDeviceBroker.startBluetoothScoForClient(
-                    cb, uid, scoAudioMode, isPrivileged, eventSource);
+                    cb, attributionSource, scoAudioMode, isPrivileged, eventSource);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -7133,13 +7155,17 @@
     }
 
     /** @see AudioManager#stopBluetoothSco() */
-    public void stopBluetoothSco(IBinder cb){
+    public void stopBluetoothSco(IBinder cb,
+            @NonNull AttributionSource attributionSource) {
+        if (attributionSource == null) {
+            return;
+        }
         if (!checkAudioSettingsPermission("stopBluetoothSco()") ||
                 !mSystemReady) {
             return;
         }
-        final int uid = Binder.getCallingUid();
-        final int pid = Binder.getCallingPid();
+        final int uid = attributionSource.getUid();
+        final int pid = attributionSource.getPid();
         final String eventSource =  new StringBuilder("stopBluetoothSco()")
                 .append(") from u/pid:").append(uid).append("/")
                 .append(pid).toString();
@@ -7147,7 +7173,8 @@
                 == PackageManager.PERMISSION_GRANTED;
         final long ident = Binder.clearCallingIdentity();
         try {
-            mDeviceBroker.stopBluetoothScoForClient(cb, uid, isPrivileged, eventSource);
+            mDeviceBroker.stopBluetoothScoForClient(
+                    cb, attributionSource, isPrivileged, eventSource);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -8672,9 +8699,14 @@
             // Only set audio policy BT SCO stream volume to 0 when the stream is actually muted.
             // This allows RX path muting by the audio HAL only when explicitly muted but not when
             // index is just set to 0 to repect BT requirements
+            boolean muted = false;
             if (mHasValidStreamType && isVssMuteBijective(mPublicStreamType)
                     && getVssForStreamOrDefault(mPublicStreamType).isFullyMuted()) {
-                index = 0;
+                if (ringMyCar()) {
+                    muted = true;
+                } else {
+                    index = 0;
+                }
             } else if (isStreamBluetoothSco(mPublicStreamType) && index == 0) {
                 index = 1;
             }
@@ -8684,13 +8716,14 @@
                         / getVssForStreamOrDefault(mPublicStreamType).getIndexStepFactor());
             }
 
+
             if (DEBUG_VOL) {
                 Log.d(TAG, "setVolumeIndexInt(" + mAudioVolumeGroup.getId() + ", " + index + ", "
-                        + device + ")");
+                        + muted + ", " + device + ")");
             }
 
             // Set the volume index
-            mAudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device);
+            mAudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, muted, device);
         }
 
         @GuardedBy("AudioService.VolumeStreamState.class")
@@ -9274,6 +9307,13 @@
             }
         }
 
+        /**
+         * Sends the new volume index on the given device to native.
+         *
+         * <p>Make sure the index is consistent with the muting state. When ringMyCar is enabled
+         * will send the non-zero index together with muted state. Otherwise, index 0  will be sent
+         * to native for signalising a muted stream.
+         **/
         @GuardedBy("VolumeStreamState.class")
         private void setStreamVolumeIndex(int index, int device) {
             // Only set audio policy BT SCO stream volume to 0 when the stream is actually muted.
@@ -9288,18 +9328,19 @@
                         / 10;
             }
 
+            boolean muted = ringMyCar() ? isFullyMuted() : false;
             if (DEBUG_VOL) {
-                Log.d(TAG, "setStreamVolumeIndexAS(" + mStreamType + ", " + index + ", " + device
-                        + ")");
+                Log.d(TAG, "setStreamVolumeIndexAS(streamType=" + mStreamType + ", index=" + index
+                        + ", muted=" + muted + ", device=" + device + ")");
             }
-            mAudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);
+            mAudioSystem.setStreamVolumeIndexAS(mStreamType, index, muted, device);
         }
 
         // must be called while synchronized VolumeStreamState.class
         @GuardedBy("VolumeStreamState.class")
         /*package*/ void applyDeviceVolume_syncVSS(int device) {
             int index;
-            if (isFullyMuted()) {
+            if (isFullyMuted() && !ringMyCar()) {
                 index = 0;
             } else if (isAbsoluteVolumeDevice(device)
                     || isA2dpAbsoluteVolumeDevice(device)
@@ -9333,7 +9374,7 @@
                 for (int i = 0; i < mIndexMap.size(); i++) {
                     final int device = mIndexMap.keyAt(i);
                     if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
-                        if (isFullyMuted()) {
+                        if (isFullyMuted() && !ringMyCar()) {
                             index = 0;
                         } else if (isAbsoluteVolumeDevice(device)
                                 || isA2dpAbsoluteVolumeDevice(device)
@@ -9368,7 +9409,7 @@
                 }
                 // apply default volume last: by convention , default device volume will be used
                 // by audio policy manager if no explicit volume is present for a given device type
-                if (isFullyMuted()) {
+                if (isFullyMuted() && !ringMyCar()) {
                     index = 0;
                 } else {
                     index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10;
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 5cabdde..e86c34c 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -547,13 +547,14 @@
      * @param device
      * @return
      */
-    public int setStreamVolumeIndexAS(int stream, int index, int device) {
-        return AudioSystem.setStreamVolumeIndexAS(stream, index, device);
+    public int setStreamVolumeIndexAS(int stream, int index, boolean muted, int device) {
+        return AudioSystem.setStreamVolumeIndexAS(stream, index, muted, device);
     }
 
     /** Same as {@link AudioSystem#setVolumeIndexForAttributes(AudioAttributes, int, int)} */
-    public int setVolumeIndexForAttributes(AudioAttributes attributes, int index, int device) {
-        return AudioSystem.setVolumeIndexForAttributes(attributes, index, device);
+    public int setVolumeIndexForAttributes(AudioAttributes attributes, int index, boolean muted,
+            int device) {
+        return AudioSystem.setVolumeIndexForAttributes(attributes, index, muted, device);
     }
 
     /**
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index a734e73..b63b07f 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -72,6 +72,7 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * Class to receive and dispatch updates from AudioSystem about recording configurations.
@@ -160,18 +161,22 @@
 
     private final Context mContext;
     private int mSavedAlarmVolume = -1;
+    private boolean mSavedAlarmMuted = false;
+    private final Function<Integer, Boolean> mIsStreamMutedCb;
     private final int mMaxAlarmVolume;
     private int mPrivilegedAlarmActiveCount = 0;
     private final Consumer<AudioDeviceAttributes> mMuteAwaitConnectionTimeoutCb;
     private final FadeOutManager mFadeOutManager = new FadeOutManager();
 
     PlaybackActivityMonitor(Context context, int maxAlarmVolume,
-            Consumer<AudioDeviceAttributes> muteTimeoutCallback) {
+            Consumer<AudioDeviceAttributes> muteTimeoutCallback,
+            Function<Integer, Boolean> isStreamMutedCb) {
         mContext = context;
         mMaxAlarmVolume = maxAlarmVolume;
         PlayMonitorClient.sListenerDeathMonitor = this;
         AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
         mMuteAwaitConnectionTimeoutCb = muteTimeoutCallback;
+        mIsStreamMutedCb = isStreamMutedCb;
         initEventHandler();
     }
 
@@ -332,8 +337,9 @@
                     if (mPrivilegedAlarmActiveCount++ == 0) {
                         mSavedAlarmVolume = AudioSystem.getStreamVolumeIndex(
                                 AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER);
+                        mSavedAlarmMuted = mIsStreamMutedCb.apply(AudioSystem.STREAM_ALARM);
                         AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM,
-                                mMaxAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
+                                mMaxAlarmVolume, /*muted=*/false, AudioSystem.DEVICE_OUT_SPEAKER);
                     }
                 } else if (event != AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
                         apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
@@ -342,7 +348,8 @@
                                 AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER) ==
                                 mMaxAlarmVolume) {
                             AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM,
-                                    mSavedAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
+                                    mSavedAlarmVolume, mSavedAlarmMuted,
+                                    AudioSystem.DEVICE_OUT_SPEAKER);
                         }
                     }
                 }
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index 1ea72d7..f66c7e1 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -36,6 +36,7 @@
 import android.util.Slog;
 
 import com.android.server.backup.Flags;
+import com.android.server.notification.NotificationBackupHelper;
 
 import com.google.android.collect.Sets;
 
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index acf4db3..0807c70 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -292,6 +292,13 @@
      */
     public float renderFrameRate;
 
+
+    /**
+     * If {@code true}, this Display supports adaptive refresh rates.
+     * @see android.view.DisplayInfo#hasArrSupport for more details.
+     */
+    public boolean hasArrSupport;
+
     /**
      * The default mode of the display.
      */
@@ -540,7 +547,8 @@
                 other.brightnessDefault)
                 || !Objects.equals(roundedCorners, other.roundedCorners)
                 || installOrientation != other.installOrientation
-                || !Objects.equals(displayShape, other.displayShape)) {
+                || !Objects.equals(displayShape, other.displayShape)
+                || hasArrSupport != other.hasArrSupport) {
             diff |= DIFF_OTHER;
         }
         return diff;
@@ -558,6 +566,7 @@
         height = other.height;
         modeId = other.modeId;
         renderFrameRate = other.renderFrameRate;
+        hasArrSupport = other.hasArrSupport;
         defaultModeId = other.defaultModeId;
         userPreferredModeId = other.userPreferredModeId;
         supportedModes = other.supportedModes;
@@ -602,6 +611,7 @@
         sb.append(width).append(" x ").append(height);
         sb.append(", modeId ").append(modeId);
         sb.append(", renderFrameRate ").append(renderFrameRate);
+        sb.append(", hasArrSupport ").append(hasArrSupport);
         sb.append(", defaultModeId ").append(defaultModeId);
         sb.append(", userPreferredModeId ").append(userPreferredModeId);
         sb.append(", supportedModes ").append(Arrays.toString(supportedModes));
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 5edea0a..f9c3a46 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -246,6 +246,7 @@
         private int mActiveModeId = INVALID_MODE_ID;
         private boolean mDisplayModeSpecsInvalid;
         private int mActiveColorMode;
+        private boolean mHasArrSupport;
         private Display.HdrCapabilities mHdrCapabilities;
         private boolean mAllmSupported;
         private boolean mGameContentTypeSupported;
@@ -311,6 +312,7 @@
             changed |= updateHdrCapabilitiesLocked(dynamicInfo.hdrCapabilities);
             changed |= updateAllmSupport(dynamicInfo.autoLowLatencyModeSupported);
             changed |= updateGameContentTypeSupport(dynamicInfo.gameContentTypeSupported);
+            changed |= updateHasArrSupportLocked(dynamicInfo.hasArrSupport);
 
             if (changed) {
                 mHavePendingChanges = true;
@@ -602,6 +604,14 @@
             return true;
         }
 
+        private boolean updateHasArrSupportLocked(boolean newHasArrSupport) {
+            if (mHasArrSupport == newHasArrSupport) {
+                return false;
+            }
+            mHasArrSupport = newHasArrSupport;
+            return true;
+        }
+
         private boolean updateAllmSupport(boolean supported) {
             if (mAllmSupported == supported) {
                 return false;
@@ -684,6 +694,7 @@
                     mInfo.supportedColorModes[i] = mSupportedColorModes.get(i);
                 }
                 mInfo.hdrCapabilities = mHdrCapabilities;
+                mInfo.hasArrSupport = mHasArrSupport;
                 mInfo.appVsyncOffsetNanos = mActiveSfDisplayMode.appVsyncOffsetNanos;
                 mInfo.presentationDeadlineNanos = mActiveSfDisplayMode.presentationDeadlineNanos;
                 mInfo.state = mState;
@@ -1274,6 +1285,7 @@
             pw.println("mActiveColorMode=" + mActiveColorMode);
             pw.println("mDefaultModeId=" + mDefaultModeId);
             pw.println("mUserPreferredModeId=" + mUserPreferredModeId);
+            pw.println("mHasArrSupport=" + mHasArrSupport);
             pw.println("mState=" + Display.stateToString(mState));
             pw.println("mCommittedState=" + Display.stateToString(mCommittedState));
             pw.println("mBrightnessState=" + mBrightnessState);
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 007e3a8..074a4d8 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -506,12 +506,13 @@
             mBaseDisplayInfo.rotation = Surface.ROTATION_0;
             mBaseDisplayInfo.modeId = deviceInfo.modeId;
             mBaseDisplayInfo.renderFrameRate = deviceInfo.renderFrameRate;
+            mBaseDisplayInfo.hasArrSupport = deviceInfo.hasArrSupport;
             mBaseDisplayInfo.defaultModeId = deviceInfo.defaultModeId;
             mBaseDisplayInfo.userPreferredModeId = deviceInfo.userPreferredModeId;
             mBaseDisplayInfo.supportedModes = Arrays.copyOf(
                     deviceInfo.supportedModes, deviceInfo.supportedModes.length);
             mBaseDisplayInfo.appsSupportedModes = syntheticModeManager.createAppSupportedModes(
-                    config, mBaseDisplayInfo.supportedModes
+                    config, mBaseDisplayInfo.supportedModes, mBaseDisplayInfo.hasArrSupport
             );
             mBaseDisplayInfo.colorMode = deviceInfo.colorMode;
             mBaseDisplayInfo.supportedColorModes = Arrays.copyOf(
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 70bf566..07343f4 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -232,6 +232,11 @@
             Flags::enableBatteryStatsForAllDisplays
     );
 
+    private final FlagState mHasArrSupport = new FlagState(
+            Flags.FLAG_ENABLE_HAS_ARR_SUPPORT,
+            Flags::enableHasArrSupport
+    );
+
     /**
      * @return {@code true} if 'port' is allowed in display layout configuration file.
      */
@@ -493,6 +498,12 @@
     }
 
     /**
+     * @return {@code true} if hasArrSupport API is enabled.
+     */
+    public boolean hasArrSupportFlag() {
+        return mHasArrSupport.isEnabled();
+    }
+    /**
      * dumps all flagstates
      * @param pw printWriter
      */
@@ -541,6 +552,7 @@
         pw.println(" " + mEnableApplyDisplayChangedDuringDisplayAdded);
         pw.println(" " + mBlockAutobrightnessChangesOnStylusUsage);
         pw.println(" " + mIsUserRefreshRateForExternalDisplayEnabled);
+        pw.println(" " + mHasArrSupport);
     }
 
     private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 602c699..ddb2969 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -414,3 +414,11 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "enable_has_arr_support"
+    namespace: "core_graphics"
+    description: "Flag for an API to get whether display supports ARR or not"
+    bug: "361433651"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index ffa64bf..88562ab 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -156,6 +156,8 @@
     // a map from display id to display device config
     private SparseArray<DisplayDeviceConfig> mDisplayDeviceConfigByDisplay = new SparseArray<>();
 
+    private SparseBooleanArray mHasArrSupport;
+
     private BrightnessObserver mBrightnessObserver;
 
     private DesiredDisplayModeSpecsListener mDesiredDisplayModeSpecsListener;
@@ -194,6 +196,8 @@
 
     private final boolean mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled;
 
+    private final boolean mHasArrSupportFlagEnabled;
+
     private final DisplayManagerFlags mDisplayManagerFlags;
 
     private final DisplayDeviceConfigProvider mDisplayDeviceConfigProvider;
@@ -218,6 +222,7 @@
             .isDisplaysRefreshRatesSynchronizationEnabled();
         mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled = displayManagerFlags
                 .isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled();
+        mHasArrSupportFlagEnabled = displayManagerFlags.hasArrSupportFlag();
         mDisplayManagerFlags = displayManagerFlags;
         mDisplayDeviceConfigProvider = displayDeviceConfigProvider;
         mContext = context;
@@ -228,6 +233,7 @@
         mSupportedModesByDisplay = new SparseArray<>();
         mAppSupportedModesByDisplay = new SparseArray<>();
         mDefaultModeByDisplay = new SparseArray<>();
+        mHasArrSupport = new SparseBooleanArray();
         mAppRequestObserver = new AppRequestObserver(displayManagerFlags);
         mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig());
         mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
@@ -452,7 +458,13 @@
         return mAppRequestObserver;
     }
 
+    // TODO(b/372019752) Rename all the occurrences of the VRR with ARR.
     private boolean isVrrSupportedLocked(int displayId) {
+        if (mHasArrSupportFlagEnabled) {
+            Boolean hasArrSupport = mHasArrSupport.get(displayId);
+            return hasArrSupport != null && hasArrSupport;
+        }
+        // TODO(b/371041638) Remove config.isVrrSupportEnabled once hasArrSupport is rolled out
         DisplayDeviceConfig config = mDisplayDeviceConfigByDisplay.get(displayId);
         return config != null && config.isVrrSupportEnabled();
     }
@@ -1469,6 +1481,7 @@
             DisplayInfo displayInfo = getDisplayInfo(displayId);
             registerExternalDisplay(displayInfo);
             updateDisplayModes(displayId, displayInfo);
+            updateHasArrSupport(displayId, displayInfo);
             updateLayoutLimitedFrameRate(displayId, displayInfo);
             updateUserSettingDisplayPreferredSize(displayInfo);
             updateDisplaysPeakRefreshRateAndResolution(displayInfo);
@@ -1482,6 +1495,7 @@
                 mDefaultModeByDisplay.remove(displayId);
                 mDisplayDeviceConfigByDisplay.remove(displayId);
                 mSettingsObserver.removeRefreshRateSetting(displayId);
+                mHasArrSupport.delete(displayId);
             }
             updateLayoutLimitedFrameRate(displayId, null);
             removeUserSettingDisplayPreferredSize(displayId);
@@ -1493,6 +1507,7 @@
         public void onDisplayChanged(int displayId) {
             updateDisplayDeviceConfig(displayId);
             DisplayInfo displayInfo = getDisplayInfo(displayId);
+            updateHasArrSupport(displayId, displayInfo);
             updateDisplayModes(displayId, displayInfo);
             updateLayoutLimitedFrameRate(displayId, displayInfo);
             updateUserSettingDisplayPreferredSize(displayInfo);
@@ -1691,6 +1706,16 @@
                 }
             }
         }
+
+        private void updateHasArrSupport(int displayId, @Nullable DisplayInfo info) {
+            if (info == null) {
+                return;
+            }
+            synchronized (mLock) {
+                mHasArrSupport.put(displayId, info.hasArrSupport);
+            }
+        }
+
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/mode/SyntheticModeManager.java b/services/core/java/com/android/server/display/mode/SyntheticModeManager.java
index a83b939..71b34679 100644
--- a/services/core/java/com/android/server/display/mode/SyntheticModeManager.java
+++ b/services/core/java/com/android/server/display/mode/SyntheticModeManager.java
@@ -37,17 +37,22 @@
             SYNTHETIC_MODE_REFRESH_RATE + FLOAT_TOLERANCE;
 
     private final boolean mSynthetic60HzModesEnabled;
+    private final boolean mHasArrSupportEnabled;
 
     public SyntheticModeManager(DisplayManagerFlags flags) {
         mSynthetic60HzModesEnabled = flags.isSynthetic60HzModesEnabled();
+        mHasArrSupportEnabled = flags.hasArrSupportFlag();
     }
 
     /**
      * creates display supportedModes array, exposed to applications
      */
     public Display.Mode[] createAppSupportedModes(DisplayDeviceConfig config,
-            Display.Mode[] modes) {
-        if (!config.isVrrSupportEnabled() || !mSynthetic60HzModesEnabled) {
+            Display.Mode[] modes, boolean hasArrSupport) {
+        // TODO(b/361433651) Remove config.isVrrSupportEnabled once hasArrSupport is rolled out
+        boolean isArrSupported =
+                mHasArrSupportEnabled ? hasArrSupport : config.isVrrSupportEnabled();
+        if (!isArrSupported || !mSynthetic60HzModesEnabled) {
             return modes;
         }
         List<Display.Mode> appSupportedModes = new ArrayList<>();
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index dc2c957..636854b 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -63,7 +63,6 @@
 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
-import com.android.server.integrity.engine.RuleEvaluationEngine;
 import com.android.server.integrity.model.IntegrityCheckResult;
 import com.android.server.integrity.model.RuleMetadata;
 import com.android.server.pm.PackageManagerServiceUtils;
@@ -130,7 +129,6 @@
     private final Handler mHandler;
     private final PackageManagerInternal mPackageManagerInternal;
     private final Supplier<PackageParser2> mParserSupplier;
-    private final RuleEvaluationEngine mEvaluationEngine;
     private final IntegrityFileManager mIntegrityFileManager;
 
     /** Create an instance of {@link AppIntegrityManagerServiceImpl}. */
@@ -142,7 +140,6 @@
                 context,
                 LocalServices.getService(PackageManagerInternal.class),
                 PackageParserUtils::forParsingFileWithDefaults,
-                RuleEvaluationEngine.getRuleEvaluationEngine(),
                 IntegrityFileManager.getInstance(),
                 handlerThread.getThreadHandler());
     }
@@ -152,13 +149,11 @@
             Context context,
             PackageManagerInternal packageManagerInternal,
             Supplier<PackageParser2> parserSupplier,
-            RuleEvaluationEngine evaluationEngine,
             IntegrityFileManager integrityFileManager,
             Handler handler) {
         mContext = context;
         mPackageManagerInternal = packageManagerInternal;
         mParserSupplier = parserSupplier;
-        mEvaluationEngine = evaluationEngine;
         mIntegrityFileManager = integrityFileManager;
         mHandler = handler;
 
@@ -330,7 +325,7 @@
                                 + " installers "
                                 + allowedInstallers);
             }
-            IntegrityCheckResult result = mEvaluationEngine.evaluate(appInstallMetadata);
+            IntegrityCheckResult result = IntegrityCheckResult.allow();
             if (!result.getMatchedRules().isEmpty() || DEBUG_INTEGRITY_COMPONENT) {
                 Slog.i(
                         TAG,
diff --git a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
deleted file mode 100644
index e8c828b..0000000
--- a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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.server.integrity.engine;
-
-import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.Rule;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.integrity.IntegrityFileManager;
-import com.android.server.integrity.model.IntegrityCheckResult;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * The engine used to evaluate rules against app installs.
- *
- * <p>Every app install is evaluated against rules (pushed by the verifier) by the evaluation engine
- * to allow/block that install.
- */
-public class RuleEvaluationEngine {
-    private static final String TAG = "RuleEvaluation";
-
-    // The engine for loading rules, retrieving metadata for app installs, and evaluating app
-    // installs against rules.
-    private static RuleEvaluationEngine sRuleEvaluationEngine;
-
-    private final IntegrityFileManager mIntegrityFileManager;
-
-    @VisibleForTesting
-    RuleEvaluationEngine(IntegrityFileManager integrityFileManager) {
-        mIntegrityFileManager = integrityFileManager;
-    }
-
-    /** Provide a singleton instance of the rule evaluation engine. */
-    public static synchronized RuleEvaluationEngine getRuleEvaluationEngine() {
-        if (sRuleEvaluationEngine == null) {
-            return new RuleEvaluationEngine(IntegrityFileManager.getInstance());
-        }
-        return sRuleEvaluationEngine;
-    }
-
-    /**
-     * Load, and match the list of rules against an app install metadata.
-     *
-     * @param appInstallMetadata Metadata of the app to be installed, and to evaluate the rules
-     *                           against.
-     * @return result of the integrity check
-     */
-    public IntegrityCheckResult evaluate(
-            AppInstallMetadata appInstallMetadata) {
-        List<Rule> rules = loadRules(appInstallMetadata);
-        return IntegrityCheckResult.allow();
-    }
-
-    private List<Rule> loadRules(AppInstallMetadata appInstallMetadata) {
-        if (!mIntegrityFileManager.initialized()) {
-            Slog.w(TAG, "Integrity rule files are not available.");
-            return Collections.emptyList();
-        }
-
-        try {
-            return mIntegrityFileManager.readRules(appInstallMetadata);
-        } catch (Exception e) {
-            Slog.e(TAG, "Error loading rules.", e);
-            return new ArrayList<>();
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/notification/CalendarTracker.java b/services/core/java/com/android/server/notification/CalendarTracker.java
index cfcf6eb..f186f42 100644
--- a/services/core/java/com/android/server/notification/CalendarTracker.java
+++ b/services/core/java/com/android/server/notification/CalendarTracker.java
@@ -32,6 +32,8 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.PrintWriter;
 import java.util.Date;
 import java.util.Objects;
@@ -296,4 +298,8 @@
         void onChanged();
     }
 
+    @VisibleForTesting // (otherwise = NONE)
+    public int getUserId() {
+        return mUserContext.getUserId();
+    }
 }
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 66e61c0..0b40d64 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -169,16 +169,15 @@
         for (int i = 0; i < mSystemConditionProviders.size(); i++) {
             mSystemConditionProviders.valueAt(i).onBootComplete();
         }
-        if (mCallback != null) {
-            mCallback.onBootComplete();
-        }
     }
 
     @Override
     public void onUserSwitched(int user) {
         super.onUserSwitched(user);
-        if (mCallback != null) {
-            mCallback.onUserSwitched();
+        if (android.app.Flags.modesHsum()) {
+            for (int i = 0; i < mSystemConditionProviders.size(); i++) {
+                mSystemConditionProviders.valueAt(i).onUserSwitched(UserHandle.of(user));
+            }
         }
     }
 
@@ -515,10 +514,8 @@
     }
 
     public interface Callback {
-        void onBootComplete();
         void onServiceAdded(ComponentName component);
         void onConditionChanged(Uri id, Condition condition);
-        void onUserSwitched();
     }
 
 }
diff --git a/services/core/java/com/android/server/notification/CountdownConditionProvider.java b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
index c3ace15..bb8ccd2 100644
--- a/services/core/java/com/android/server/notification/CountdownConditionProvider.java
+++ b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.Uri;
+import android.os.UserHandle;
 import android.service.notification.Condition;
 import android.service.notification.ZenModeConfig;
 import android.text.format.DateUtils;
@@ -65,6 +66,11 @@
     }
 
     @Override
+    public void onUserSwitched(UserHandle user) {
+        // Nothing to do because countdown conditions are not tied to any user data.
+    }
+
+    @Override
     public void dump(PrintWriter pw, DumpFilter filter) {
         pw.println("    CountdownConditionProvider:");
         pw.print("      mConnected="); pw.println(mConnected);
diff --git a/services/core/java/com/android/server/notification/CustomManualConditionProvider.java b/services/core/java/com/android/server/notification/CustomManualConditionProvider.java
index 9531c5e..52dc116 100644
--- a/services/core/java/com/android/server/notification/CustomManualConditionProvider.java
+++ b/services/core/java/com/android/server/notification/CustomManualConditionProvider.java
@@ -17,6 +17,7 @@
 package com.android.server.notification;
 
 import android.net.Uri;
+import android.os.UserHandle;
 import android.service.notification.ZenModeConfig;
 
 import java.io.PrintWriter;
@@ -40,6 +41,11 @@
     }
 
     @Override
+    public void onUserSwitched(UserHandle user) {
+        // Nothing to do because we won't ever call notifyConditions.
+    }
+
+    @Override
     public void onConnected() {
         // No need to keep subscriptions because we won't ever call notifyConditions
     }
diff --git a/services/core/java/com/android/server/notification/EventConditionProvider.java b/services/core/java/com/android/server/notification/EventConditionProvider.java
index 00dd547..ecc4cf7 100644
--- a/services/core/java/com/android/server/notification/EventConditionProvider.java
+++ b/services/core/java/com/android/server/notification/EventConditionProvider.java
@@ -16,6 +16,7 @@
 
 package com.android.server.notification;
 
+import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -23,6 +24,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -37,6 +39,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.notification.CalendarTracker.CheckEventResult;
 import com.android.server.notification.NotificationManagerService.DumpFilter;
 import com.android.server.pm.PackageManagerService;
@@ -59,12 +62,13 @@
     private static final String EXTRA_TIME = "time";
     private static final long CHANGE_DELAY = 2 * 1000;  // coalesce chatty calendar changes
 
-    private final Context mContext = this;
+    @VisibleForTesting Context mContext = this;
     private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
     private final SparseArray<CalendarTracker> mTrackers = new SparseArray<>();
     private final Handler mWorker;
     private final HandlerThread mThread;
 
+    @Nullable private UserHandle mCurrentUser;
     private boolean mConnected;
     private boolean mRegistered;
     private boolean mBootComplete;  // don't hammer the calendar provider until boot completes.
@@ -114,10 +118,28 @@
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                reloadTrackers();
+                if (android.app.Flags.modesHsum()) {
+                    if (mCurrentUser != null) {
+                        // Possibly the intent signals a profile added on a different user, but it
+                        // doesn't matter (except for a bit of wasted work here). We will reload
+                        // trackers for that user when we switch.
+                        reloadTrackers(mCurrentUser);
+                    }
+                } else {
+                    reloadTrackers();
+                }
             }
         }, filter);
-        reloadTrackers();
+        if (!android.app.Flags.modesHsum()) {
+            reloadTrackers();
+        }
+    }
+
+    @Override
+    public void onUserSwitched(UserHandle user) {
+        if (DEBUG) Slog.d(TAG, "onUserSwitched: " + user);
+        mCurrentUser = user;
+        reloadTrackers(user);
     }
 
     @Override
@@ -157,8 +179,41 @@
         }
     }
 
+    private void reloadTrackers(UserHandle user) {
+        if (DEBUG) Slog.d(TAG, "reloadTrackers user=" + user);
+        for (int i = 0; i < mTrackers.size(); i++) {
+            mTrackers.valueAt(i).setCallback(null);
+        }
+        mTrackers.clear();
+
+        // Ensure that user is the main user and not a profile.
+        UserManager userManager = UserManager.get(mContext);
+        UserHandle possibleParent = userManager.getProfileParent(user);
+        if (possibleParent != null) {
+            Slog.wtf(TAG, "reloadTrackers should not be called with profile " + user
+                    + "; continuing with parent " + possibleParent);
+            user = possibleParent;
+        }
+
+        for (UserInfo profile : userManager.getProfiles(user.getIdentifier())) {
+            final Context profileContext = getContextForUser(mContext, profile.getUserHandle());
+            if (profileContext == null) {
+                Slog.w(TAG, "Unable to create context for profile " + profile.id + " of user "
+                        + user.getIdentifier());
+                continue;
+            }
+            mTrackers.put(profile.id, new CalendarTracker(mContext, profileContext));
+        }
+        evaluateSubscriptions();
+    }
+
+    @Deprecated // Remove when inlining MODES_HSUM
     private void reloadTrackers() {
         if (DEBUG) Slog.d(TAG, "reloadTrackers");
+        if (android.app.Flags.modesHsum()) {
+            Slog.wtf(TAG, "Shouldn't call reloadTrackers() without user in MODES_HSUM",
+                    new Exception());
+        }
         for (int i = 0; i < mTrackers.size(); i++) {
             mTrackers.valueAt(i).setCallback(null);
         }
@@ -325,4 +380,9 @@
             evaluateSubscriptionsW();
         }
     };
+
+    @VisibleForTesting // (otherwise = NONE)
+    public SparseArray<CalendarTracker> getTrackers() {
+        return mTrackers;
+    }
 }
diff --git a/core/java/com/android/server/backup/NotificationBackupHelper.java b/services/core/java/com/android/server/notification/NotificationBackupHelper.java
similarity index 67%
rename from core/java/com/android/server/backup/NotificationBackupHelper.java
rename to services/core/java/com/android/server/notification/NotificationBackupHelper.java
index faa0509..ee9ec15 100644
--- a/core/java/com/android/server/backup/NotificationBackupHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationBackupHelper.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.backup;
+package com.android.server.notification;
 
 import android.app.INotificationManager;
 import android.app.backup.BlobBackupHelper;
@@ -22,6 +22,8 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.server.LocalServices;
+
 public class NotificationBackupHelper extends BlobBackupHelper {
     static final String TAG = "NotifBackupHelper";   // must be < 23 chars
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -34,9 +36,13 @@
 
     private final int mUserId;
 
+    private final NotificationManagerInternal mNm;
+
     public NotificationBackupHelper(int userId) {
         super(BLOB_VERSION, KEY_NOTIFICATIONS);
         mUserId = userId;
+
+        mNm = LocalServices.getService(NotificationManagerInternal.class);
     }
 
     @Override
@@ -44,9 +50,13 @@
         byte[] newPayload = null;
         if (KEY_NOTIFICATIONS.equals(key)) {
             try {
-                INotificationManager nm = INotificationManager.Stub.asInterface(
-                        ServiceManager.getService("notification"));
-                newPayload = nm.getBackupPayload(mUserId);
+                if (android.app.Flags.backupRestoreLogging()) {
+                    newPayload = mNm.getBackupPayload(mUserId, getLogger());
+                } else {
+                    INotificationManager nm = INotificationManager.Stub.asInterface(
+                            ServiceManager.getService("notification"));
+                    newPayload = nm.getBackupPayload(mUserId);
+                }
             } catch (Exception e) {
                 // Treat as no data
                 Slog.e(TAG, "Couldn't communicate with notification manager", e);
@@ -64,9 +74,13 @@
 
         if (KEY_NOTIFICATIONS.equals(key)) {
             try {
-                INotificationManager nm = INotificationManager.Stub.asInterface(
-                        ServiceManager.getService("notification"));
-                nm.applyRestore(payload, mUserId);
+                if (android.app.Flags.backupRestoreLogging()) {
+                    mNm.applyRestore(payload, mUserId, getLogger());
+                } else {
+                    INotificationManager nm = INotificationManager.Stub.asInterface(
+                            ServiceManager.getService("notification"));
+                    nm.applyRestore(payload, mUserId);
+                }
             } catch (Exception e) {
                 Slog.e(TAG, "Couldn't communicate with notification manager", e);
             }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index 4b8de4e..f3d6a2d 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -19,6 +19,7 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
+import android.app.backup.BackupRestoreEventLogger;
 import android.service.notification.DeviceEffectsApplier;
 
 import java.util.Set;
@@ -73,4 +74,9 @@
      * Otherwise an {@link IllegalStateException} will be thrown.
      */
     void setDeviceEffectsApplier(DeviceEffectsApplier applier);
+
+    // Backup/restore interface
+    byte[] getBackupPayload(int user, BackupRestoreEventLogger logger);
+
+    void applyRestore(byte[] payload, int user, BackupRestoreEventLogger logger);
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 88334eb..828e02c 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -216,6 +216,7 @@
 import android.app.UriGrantsManager;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.backup.BackupManager;
+import android.app.backup.BackupRestoreEventLogger;
 import android.app.compat.CompatChanges;
 import android.app.role.OnRoleHoldersChangedListener;
 import android.app.role.RoleManager;
@@ -462,7 +463,7 @@
     static final int INVALID_UID = -1;
     static final String ROOT_PKG = "root";
 
-    static final String[] ALLOWED_ADJUSTMENTS = new String[] {
+    static final String[] DEFAULT_ALLOWED_ADJUSTMENTS = new String[] {
             Adjustment.KEY_PEOPLE,
             Adjustment.KEY_SNOOZE_CRITERIA,
             Adjustment.KEY_USER_SENTIMENT,
@@ -1102,7 +1103,8 @@
         return false;
     }
 
-    void readPolicyXml(InputStream stream, boolean forRestore, int userId)
+    void readPolicyXml(InputStream stream, boolean forRestore, int userId,
+            BackupRestoreEventLogger logger)
             throws XmlPullParserException, NumberFormatException, IOException {
         final TypedXmlPullParser parser;
         if (forRestore) {
@@ -1189,7 +1191,7 @@
             InputStream infile = null;
             try {
                 infile = mPolicyFile.openRead();
-                readPolicyXml(infile, false /*forRestore*/, USER_ALL);
+                readPolicyXml(infile, false /*forRestore*/, USER_ALL, null);
 
                 // We re-load the default dnd packages to allow the newly added and denined.
                 final boolean isWatch = mPackageManagerClient.hasSystemFeature(
@@ -1239,7 +1241,7 @@
                 }
 
                 try {
-                    writePolicyXml(stream, false /*forBackup*/, USER_ALL);
+                    writePolicyXml(stream, false /*forBackup*/, USER_ALL, null);
                     mPolicyFile.finishWrite(stream);
                 } catch (IOException e) {
                     Slog.w(TAG, "Failed to save policy file, restoring backup", e);
@@ -1250,8 +1252,8 @@
         }
     }
 
-    private void writePolicyXml(OutputStream stream, boolean forBackup, int userId)
-            throws IOException {
+    private void writePolicyXml(OutputStream stream, boolean forBackup, int userId,
+            BackupRestoreEventLogger logger)  throws IOException {
         final TypedXmlSerializer out;
         if (forBackup) {
             out = Xml.newFastSerializer();
@@ -4119,6 +4121,24 @@
 
         @Override
         @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+        public void allowAssistantAdjustment(String adjustmentType) {
+            checkCallerIsSystemOrSystemUiOrShell();
+            mAssistants.allowAdjustmentType(adjustmentType);
+
+            handleSavePolicyFile();
+        }
+
+        @Override
+        @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+        public void disallowAssistantAdjustment(String adjustmentType) {
+            checkCallerIsSystemOrSystemUiOrShell();
+            mAssistants.disallowAdjustmentType(adjustmentType);
+
+            handleSavePolicyFile();
+        }
+
+        @Override
+        @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
         public void setAdjustmentTypeSupportedState(INotificationListener token,
                 @Adjustment.Keys String key, boolean supported) {
             final long identity = Binder.clearCallingIdentity();
@@ -4133,6 +4153,7 @@
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
+            handleSavePolicyFile();
         }
 
         @Override
@@ -4891,7 +4912,7 @@
                     throw new SecurityException("Not currently an assistant");
             }
 
-            return mAssistants.getAllowedAssistantAdjustments();
+            return new ArrayList<>(mAssistants.getAllowedAssistantAdjustments());
         }
 
         /**
@@ -6171,7 +6192,7 @@
             if (DBG) Slog.d(TAG, "getBackupPayload u=" + user);
             final ByteArrayOutputStream baos = new ByteArrayOutputStream();
             try {
-                writePolicyXml(baos, true /*forBackup*/, user);
+                writePolicyXml(baos, true /*forBackup*/, user, null);
                 return baos.toByteArray();
             } catch (IOException e) {
                 Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
@@ -6190,7 +6211,7 @@
             }
             final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
             try {
-                readPolicyXml(bais, true /*forRestore*/, user);
+                readPolicyXml(bais, true /*forRestore*/, user, null);
                 handleSavePolicyFile();
             } catch (NumberFormatException | XmlPullParserException | IOException e) {
                 Slog.w(TAG, "applyRestore: error reading payload", e);
@@ -7392,6 +7413,37 @@
      */
     private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() {
 
+        public byte[] getBackupPayload(int user, BackupRestoreEventLogger logger) {
+            checkCallerIsSystem();
+            if (DBG) Slog.d(TAG, "getBackupPayload u=" + user);
+            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            try {
+                writePolicyXml(baos, true /*forBackup*/, user, logger);
+                return baos.toByteArray();
+            } catch (IOException e) {
+                Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
+            }
+            return null;
+        }
+
+        @Override
+        public void applyRestore(byte[] payload, int user, BackupRestoreEventLogger logger) {
+            checkCallerIsSystem();
+            if (DBG) Slog.d(TAG, "applyRestore u=" + user + " payload="
+                    + (payload != null ? new String(payload, StandardCharsets.UTF_8) : null));
+            if (payload == null) {
+                Slog.w(TAG, "applyRestore: no payload to restore for user " + user);
+                return;
+            }
+            final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
+            try {
+                readPolicyXml(bais, true /*forRestore*/, user, logger);
+                handleSavePolicyFile();
+            } catch (NumberFormatException | XmlPullParserException | IOException e) {
+                Slog.w(TAG, "applyRestore: error reading payload", e);
+            }
+        }
+
         @Override
         public NotificationChannel getNotificationChannel(String pkg, int uid, String
                 channelId) {
@@ -11395,6 +11447,7 @@
         static final String TAG_ENABLED_NOTIFICATION_ASSISTANTS = "enabled_assistants";
 
         private static final String ATT_TYPES = "types";
+        private static final String ATT_DENIED = "denied_adjustments";
         private static final String ATT_NAS_UNSUPPORTED = "unsupported_adjustments";
 
         private final Object mLock = new Object();
@@ -11403,6 +11456,9 @@
         private Set<String> mAllowedAdjustments = new ArraySet<>();
 
         @GuardedBy("mLock")
+        private Set<String> mDeniedAdjustments = new ArraySet<>();
+
+        @GuardedBy("mLock")
         private Map<Integer, HashSet<String>> mNasUnsupported = new ArrayMap<>();
 
         protected ComponentName mDefaultFromConfig = null;
@@ -11474,9 +11530,11 @@
                 IPackageManager pm) {
             super(context, lock, up, pm);
 
-            // Add all default allowed adjustment types.
-            for (int i = 0; i < ALLOWED_ADJUSTMENTS.length; i++) {
-                mAllowedAdjustments.add(ALLOWED_ADJUSTMENTS[i]);
+            if (!notificationClassification()) {
+                // Add all default allowed adjustment types.
+                for (int i = 0; i < DEFAULT_ALLOWED_ADJUSTMENTS.length; i++) {
+                    mAllowedAdjustments.add(DEFAULT_ALLOWED_ADJUSTMENTS[i]);
+                }
             }
         }
 
@@ -11539,17 +11597,28 @@
             return android.Manifest.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE;
         }
 
-        protected List<String> getAllowedAssistantAdjustments() {
+        protected Set<String> getAllowedAssistantAdjustments() {
             synchronized (mLock) {
-                List<String> types = new ArrayList<>();
-                types.addAll(mAllowedAdjustments);
-                return types;
+                if (notificationClassification()) {
+                    Set<String> types = new HashSet<>(Set.of(DEFAULT_ALLOWED_ADJUSTMENTS));
+                    types.removeAll(mDeniedAdjustments);
+                    return types;
+                } else {
+                    Set<String> types = new HashSet<>();
+                    types.addAll(mAllowedAdjustments);
+                    return types;
+                }
             }
         }
 
         protected boolean isAdjustmentAllowed(String type) {
             synchronized (mLock) {
-                return mAllowedAdjustments.contains(type);
+                if (notificationClassification()) {
+                    return List.of(DEFAULT_ALLOWED_ADJUSTMENTS).contains(type)
+                            && !mDeniedAdjustments.contains(type);
+                } else {
+                    return mAllowedAdjustments.contains(type);
+                }
             }
         }
 
@@ -11911,6 +11980,30 @@
 
         @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
         @GuardedBy("mNotificationLock")
+        public void allowAdjustmentType(@Adjustment.Keys String key) {
+            if (!android.service.notification.Flags.notificationClassification()) {
+                return;
+            }
+            mDeniedAdjustments.remove(key);
+            for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
+                mHandler.post(() -> notifyCapabilitiesChanged(info));
+            }
+        }
+
+        @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+        @GuardedBy("mNotificationLock")
+        public void disallowAdjustmentType(@Adjustment.Keys String key) {
+            if (!android.service.notification.Flags.notificationClassification()) {
+                return;
+            }
+            mDeniedAdjustments.add(key);
+            for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
+                mHandler.post(() -> notifyCapabilitiesChanged(info));
+            }
+        }
+
+        @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+        @GuardedBy("mNotificationLock")
         public void setAdjustmentTypeSupportedState(ManagedServiceInfo info,
                 @Adjustment.Keys String key, boolean supported) {
             if (!android.service.notification.Flags.notificationClassification()) {
@@ -11965,6 +12058,43 @@
                 }
             }
         }
+
+        @Override
+        protected void writeExtraXmlTags(TypedXmlSerializer out) throws IOException {
+            if (!android.service.notification.Flags.notificationClassification()) {
+                return;
+            }
+            synchronized (mLock) {
+                out.startTag(null, ATT_DENIED);
+                out.attribute(null, ATT_TYPES, TextUtils.join(",", mDeniedAdjustments));
+                out.endTag(null, ATT_DENIED);
+            }
+        }
+
+        @Override
+        protected void readExtraTag(String tag, TypedXmlPullParser parser) throws IOException {
+            if (!android.service.notification.Flags.notificationClassification()) {
+                return;
+            }
+            if (ATT_DENIED.equals(tag)) {
+                final String types = XmlUtils.readStringAttribute(parser, ATT_TYPES);
+                synchronized (mLock) {
+                    mDeniedAdjustments.clear();
+                    if (!TextUtils.isEmpty(types)) {
+                        mDeniedAdjustments.addAll(Arrays.asList(types.split(",")));
+                    }
+                }
+            }
+        }
+
+        private void notifyCapabilitiesChanged(final ManagedServiceInfo info) {
+            final INotificationListener assistant = (INotificationListener) info.service;
+            try {
+                assistant.onAllowedAdjustmentsChanged();
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "unable to notify assistant (capabilities): " + info, ex);
+            }
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 3ba9384..93f512b 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -334,7 +334,7 @@
             return helper.createWaveformVibration(vibrationPattern, insistent);
         }
 
-        if (com.android.server.notification.Flags.notificationVibrationInSoundUri()) {
+        if (com.android.server.notification.Flags.notificationVibrationInSoundUriForChannel()) {
             final VibrationEffect vibrationEffectFromSoundUri =
                     helper.createVibrationEffectFromSoundUri(channel.getSound());
             if (vibrationEffectFromSoundUri != null) {
diff --git a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
index 6efe88f..24b090c 100644
--- a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
+++ b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
@@ -25,6 +25,7 @@
 import android.content.IntentFilter;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.notification.Condition;
 import android.service.notification.ScheduleCalendar;
@@ -115,6 +116,11 @@
     }
 
     @Override
+    public void onUserSwitched(UserHandle user) {
+        // Nothing to do because time-based schedules are not tied to any user data.
+    }
+
+    @Override
     public void onDestroy() {
         super.onDestroy();
         if (DEBUG) Slog.d(TAG, "onDestroy");
diff --git a/services/core/java/com/android/server/notification/SystemConditionProviderService.java b/services/core/java/com/android/server/notification/SystemConditionProviderService.java
index 97073b7..656f9df 100644
--- a/services/core/java/com/android/server/notification/SystemConditionProviderService.java
+++ b/services/core/java/com/android/server/notification/SystemConditionProviderService.java
@@ -19,6 +19,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.net.Uri;
+import android.os.UserHandle;
 import android.service.notification.ConditionProviderService;
 import android.service.notification.IConditionProvider;
 import android.util.TimeUtils;
@@ -30,9 +31,10 @@
 
 public abstract class SystemConditionProviderService extends ConditionProviderService {
 
-    abstract public void dump(PrintWriter pw, DumpFilter filter);
-    abstract public boolean isValidConditionId(Uri id);
-    abstract public void onBootComplete();
+    public abstract void dump(PrintWriter pw, DumpFilter filter);
+    public abstract boolean isValidConditionId(Uri id);
+    public abstract void onBootComplete();
+    public abstract void onUserSwitched(UserHandle user);
 
     final ComponentName getComponent() {
         return new ComponentName("android", this.getClass().getName());
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index 50bfbc3..b1f010c 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -102,16 +102,6 @@
     }
 
     @Override
-    public void onBootComplete() {
-        // noop
-    }
-
-    @Override
-    public void onUserSwitched() {
-        // noop
-    }
-
-    @Override
     public void onServiceAdded(ComponentName component) {
         if (DEBUG) Log.d(TAG, "onServiceAdded " + component);
         final int callingUid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 02afdd6..9e8598a 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -89,6 +89,7 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
@@ -893,7 +894,8 @@
 
         @Override
         public void onApexStaged(@NonNull ApexStagedEvent event) {
-            mArtManager.onApexStaged(event.stagedApexModuleNames);
+            mArtManager.onApexStaged(Arrays.stream(event.stagedApexInfos)
+                    .map(info -> info.moduleName).toArray(String[]::new));
         }
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerNative.java b/services/core/java/com/android/server/pm/PackageManagerNative.java
index 66ecd6e..7d8573e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerNative.java
+++ b/services/core/java/com/android/server/pm/PackageManagerNative.java
@@ -20,7 +20,6 @@
 
 import static com.android.server.pm.PackageManagerService.TAG;
 
-import android.annotation.Nullable;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManagerNative;
 import android.content.pm.IStagedApexObserver;
@@ -184,14 +183,8 @@
     }
 
     @Override
-    public String[] getStagedApexModuleNames() {
-        return mPm.mInstallerService.getStagingManager()
-                .getStagedApexModuleNames().toArray(new String[0]);
-    }
-
-    @Override
-    @Nullable
-    public StagedApexInfo getStagedApexInfo(String moduleName) {
-        return mPm.mInstallerService.getStagingManager().getStagedApexInfo(moduleName);
+    public StagedApexInfo[] getStagedApexInfos() {
+        return mPm.mInstallerService.getStagingManager().getStagedApexInfos().toArray(
+                new StagedApexInfo[0]);
     }
 }
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 94bdfbd..38cf0b9 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -17,7 +17,6 @@
 package com.android.server.pm;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.apex.ApexInfo;
 import android.apex.ApexSessionInfo;
 import android.apex.ApexSessionParams;
@@ -38,7 +37,6 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Slog;
@@ -65,9 +63,9 @@
 import java.io.FileReader;
 import java.io.FileWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
@@ -807,14 +805,13 @@
     }
 
     /**
-     * Returns ApexInfo about APEX contained inside the session as a {@code Map<String, ApexInfo>},
-     * where the key of the map is the module name of the ApexInfo.
+     * Returns ApexInfo about APEX contained inside the session.
      *
-     * Returns an empty map if there is any error.
+     * Returns an empty list if there is any error.
      */
     @VisibleForTesting
     @NonNull
-    Map<String, ApexInfo> getStagedApexInfos(@NonNull StagedSession session) {
+    List<ApexInfo> getStagedApexInfos(@NonNull StagedSession session) {
         Preconditions.checkArgument(session != null, "Session is null");
         Preconditions.checkArgument(!session.hasParentSessionId(),
                 session.sessionId() + " session has parent session");
@@ -824,7 +821,7 @@
         // Even if caller calls this method on ready session, the session could be abandoned
         // right after this method is called.
         if (!session.isSessionReady() || session.isDestroyed()) {
-            return Collections.emptyMap();
+            return Collections.emptyList();
         }
 
         ApexSessionParams params = new ApexSessionParams();
@@ -838,20 +835,17 @@
             }
         }
         params.childSessionIds = childSessionIds.toArray();
-
-        ApexInfo[] infos = mApexManager.getStagedApexInfos(params);
-        Map<String, ApexInfo> result = new ArrayMap<>();
-        for (ApexInfo info : infos) {
-            result.put(info.moduleName, info);
-        }
-        return result;
+        return Arrays.asList(mApexManager.getStagedApexInfos(params));
     }
 
     /**
-     * Returns apex module names of all packages that are staged ready
+     * Returns ApexInfo list about APEXes contained inside all staged sessions.
+     *
+     * Returns an empty list if there is any error.
      */
-    List<String> getStagedApexModuleNames() {
-        List<String> result = new ArrayList<>();
+    @NonNull
+    List<StagedApexInfo> getStagedApexInfos() {
+        List<StagedApexInfo> result = new ArrayList<>();
         synchronized (mStagedSessions) {
             for (int i = 0; i < mStagedSessions.size(); i++) {
                 final StagedSession session = mStagedSessions.valueAt(i);
@@ -859,26 +853,7 @@
                         || session.hasParentSessionId() || !session.containsApexSession()) {
                     continue;
                 }
-                result.addAll(getStagedApexInfos(session).keySet());
-            }
-        }
-        return result;
-    }
-
-    /**
-     * Returns ApexInfo of the {@code moduleInfo} provided if it is staged, otherwise returns null.
-     */
-    @Nullable
-    StagedApexInfo getStagedApexInfo(String moduleName) {
-        synchronized (mStagedSessions) {
-            for (int i = 0; i < mStagedSessions.size(); i++) {
-                final StagedSession session = mStagedSessions.valueAt(i);
-                if (!session.isSessionReady() || session.isDestroyed()
-                        || session.hasParentSessionId() || !session.containsApexSession()) {
-                    continue;
-                }
-                ApexInfo ai = getStagedApexInfos(session).get(moduleName);
-                if (ai != null) {
+                getStagedApexInfos(session).stream().map(ai -> {
                     StagedApexInfo info = new StagedApexInfo();
                     info.moduleName = ai.moduleName;
                     info.diskImagePath = ai.modulePath;
@@ -886,17 +861,19 @@
                     info.versionName = ai.versionName;
                     info.hasClassPathJars = ai.hasClassPathJars;
                     return info;
-                }
+                }).forEach(result::add);
             }
         }
-        return null;
+        return result;
     }
 
     private void notifyStagedApexObservers() {
         synchronized (mStagedApexObservers) {
+            List<StagedApexInfo> stagedApexInfos = getStagedApexInfos();
+            ApexStagedEvent event = new ApexStagedEvent();
+            event.stagedApexInfos =
+                    stagedApexInfos.toArray(new StagedApexInfo[stagedApexInfos.size()]);
             for (IStagedApexObserver observer : mStagedApexObservers) {
-                ApexStagedEvent event = new ApexStagedEvent();
-                event.stagedApexModuleNames = getStagedApexModuleNames().toArray(new String[0]);
                 try {
                     observer.onApexStaged(event);
                 } catch (RemoteException re) {
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index 5f704a0..6f1e15b 100644
--- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -29,7 +29,6 @@
 import android.net.ConnectivityManager;
 import android.net.IpSecTransformState;
 import android.net.Network;
-import android.net.vcn.Flags;
 import android.net.vcn.VcnManager;
 import android.os.Handler;
 import android.os.HandlerExecutor;
@@ -233,7 +232,7 @@
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     static int getMaxSeqNumIncreasePerSecond(@Nullable PersistableBundleWrapper carrierConfig) {
         int maxSeqNumIncrease = MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED;
-        if (Flags.handleSeqNumLeap() && carrierConfig != null) {
+        if (carrierConfig != null) {
             maxSeqNumIncrease =
                     carrierConfig.getInt(
                             VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY,
@@ -287,10 +286,8 @@
         // with the new interval
         mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
 
-        if (Flags.handleSeqNumLeap()) {
-            mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
-            mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);
-        }
+        mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
+        mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);
 
         if (canStart() != isStarted()) {
             if (canStart()) {
@@ -438,13 +435,10 @@
                 onValidationResultReceivedInternal(true /* isFailed */);
             }
 
-            // In both "valid" or "unusual_seq_num_leap" cases, trigger network validation
-            if (Flags.validateNetworkOnIpsecLoss()) {
-                // Trigger re-validation of the underlying network; if it fails, the VCN will
-                // attempt to migrate away.
-                mConnectivityManager.reportNetworkConnectivity(
-                        getNetwork(), false /* hasConnectivity */);
-            }
+            // In both "invalid" and "unusual_seq_num_leap" cases, trigger network validation. If
+            // validation fails, the VCN will attempt to migrate away.
+            mConnectivityManager.reportNetworkConnectivity(
+                    getNetwork(), false /* hasConnectivity */);
         }
     }
 
@@ -474,8 +468,7 @@
             boolean isUnusualSeqNumLeap = false;
 
             // Handle sequence number leap
-            if (Flags.handleSeqNumLeap()
-                    && maxSeqNumIncreasePerSecond != MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) {
+            if (maxSeqNumIncreasePerSecond != MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) {
                 final long timeDiffMillis =
                         newState.getTimestampMillis() - oldState.getTimestampMillis();
                 final long maxSeqNumIncrease = timeDiffMillis * maxSeqNumIncreasePerSecond / 1000;
@@ -506,7 +499,7 @@
                             + " actualPktCntDiff: "
                             + actualPktCntDiff);
 
-            if (Flags.handleSeqNumLeap() && expectedPktCntDiff < MIN_VALID_EXPECTED_RX_PACKET_NUM) {
+            if (expectedPktCntDiff < MIN_VALID_EXPECTED_RX_PACKET_NUM) {
                 // The sample size is too small to ensure a reliable detection result
                 return PacketLossCalculationResult.invalid();
             }
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
index 78e06d4..c852fb4 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
@@ -25,7 +25,6 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
-import android.net.vcn.Flags;
 import android.net.vcn.VcnManager;
 import android.net.vcn.VcnUnderlyingNetworkTemplate;
 import android.os.Handler;
@@ -297,10 +296,8 @@
         updatePriorityClass(
                 underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
 
-        if (Flags.evaluateIpsecLossOnLpNcChange()) {
-            for (NetworkMetricMonitor monitor : mMetricMonitors) {
-                monitor.onLinkPropertiesOrCapabilitiesChanged();
-            }
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.onLinkPropertiesOrCapabilitiesChanged();
         }
     }
 
@@ -316,10 +313,8 @@
         updatePriorityClass(
                 underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
 
-        if (Flags.evaluateIpsecLossOnLpNcChange()) {
-            for (NetworkMetricMonitor monitor : mMetricMonitors) {
-                monitor.onLinkPropertiesOrCapabilitiesChanged();
-            }
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.onLinkPropertiesOrCapabilitiesChanged();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a44eb48..460de01 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -285,9 +285,6 @@
 import android.app.servertransaction.TopResumedActivityChangeItem;
 import android.app.servertransaction.TransferSplashScreenViewStateItem;
 import android.app.usage.UsageEvents.Event;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
-import android.compat.annotation.Overridable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -467,11 +464,6 @@
     // finished destroying itself.
     private static final int DESTROY_TIMEOUT = 10 * 1000;
 
-    @ChangeId
-    @Overridable
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
-    static final long UNIVERSAL_RESIZABLE_BY_DEFAULT = 357141415;
-
     final ActivityTaskManagerService mAtmService;
     final ActivityCallerState mCallerState;
     @NonNull
@@ -3192,7 +3184,7 @@
                 && mDisplayContent != null && mDisplayContent.getConfiguration()
                     .smallestScreenWidthDp >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
                 && mDisplayContent.getIgnoreOrientationRequest()
-                && info.isChangeEnabled(UNIVERSAL_RESIZABLE_BY_DEFAULT);
+                && info.isChangeEnabled(ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT);
         if (!compatEnabled && !mWmService.mConstants.mIgnoreActivityOrientationRequest) {
             return false;
         }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 3d6b64b..3560565 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -589,7 +589,7 @@
      * sensitive environment.
      */
     public abstract TaskSnapshot getTaskSnapshotBlocking(int taskId,
-            boolean isLowResolution);
+            boolean isLowResolution, @TaskSnapshot.ReferenceFlags int usage);
 
     /** Returns true if uid is considered foreground for activity start purposes. */
     public abstract boolean isUidForeground(int uid);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3dfc8f4..4db478a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3946,6 +3946,28 @@
         }
     }
 
+    private TaskSnapshot getTaskSnapshotInner(int taskId, boolean isLowResolution,
+            @TaskSnapshot.ReferenceFlags int usage) {
+        final Task task;
+        synchronized (mGlobalLock) {
+            task = mRootWindowContainer.anyTaskForId(taskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
+            if (task == null) {
+                Slog.w(TAG, "getTaskSnapshot: taskId=" + taskId + " not found");
+                return null;
+            }
+            // Try to load snapshot from cache first, and add reference if the snapshot is in cache.
+            final TaskSnapshot snapshot = mWindowManager.mTaskSnapshotController.getSnapshot(taskId,
+                    task.mUserId, false /* restoreFromDisk */, isLowResolution);
+            if (snapshot != null) {
+                snapshot.addReference(usage);
+                return snapshot;
+            }
+        }
+        // Don't call this while holding the lock as this operation might hit the disk.
+        return mWindowManager.mTaskSnapshotController.getSnapshot(taskId,
+                task.mUserId, true /* restoreFromDisk */, isLowResolution);
+    }
+
     @Override
     public TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution) {
         mAmInternal.enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
@@ -7274,8 +7296,9 @@
 
         @Override
         public TaskSnapshot getTaskSnapshotBlocking(
-                int taskId, boolean isLowResolution) {
-            return ActivityTaskManagerService.this.getTaskSnapshot(taskId, isLowResolution);
+                int taskId, boolean isLowResolution, @TaskSnapshot.ReferenceFlags int usage) {
+            return ActivityTaskManagerService.this.getTaskSnapshotInner(
+                    taskId, isLowResolution, usage);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 515f148..a380ba1 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -558,7 +558,7 @@
                     .append(mBalAllowedByPiCreatorWithHardening);
             sb.append("; resultIfPiCreatorAllowsBal: ").append(mResultForCaller);
             sb.append("; callerStartMode: ").append(balStartModeToString(
-                    mCheckedOptions.getPendingIntentBackgroundActivityStartMode()));
+                    mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()));
             sb.append("; hasRealCaller: ").append(hasRealCaller());
             sb.append("; isCallForResult: ").append(mIsCallForResult);
             sb.append("; isPendingIntent: ").append(isPendingIntent());
@@ -585,7 +585,7 @@
                 sb.append("; balAllowedByPiSender: ").append(mBalAllowedByPiSender);
                 sb.append("; resultIfPiSenderAllowsBal: ").append(mResultForRealCaller);
                 sb.append("; realCallerStartMode: ").append(balStartModeToString(
-                        mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()));
+                        mCheckedOptions.getPendingIntentBackgroundActivityStartMode()));
             }
             // features
             sb.append("; balImproveRealCallerVisibilityCheck: ")
@@ -792,7 +792,8 @@
             // Allowed before V by creator
             if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) {
                 Slog.wtf(TAG, "With Android 15 BAL hardening this activity start may be blocked"
-                        + " if the PI creator upgrades target_sdk to 35+! "
+                        + " if the PI creator upgrades target_sdk to 35+!"
+                        + " goo.gle/android-bal"
                         + " (missing opt in by PI creator)!" + state);
                 return allowBasedOnCaller(state);
             }
@@ -802,6 +803,7 @@
             if (state.mBalAllowedByPiSender.allowsBackgroundActivityStarts()) {
                 Slog.wtf(TAG, "With Android 14 BAL hardening this activity start will be blocked"
                         + " if the PI sender upgrades target_sdk to 34+! "
+                        + " goo.gle/android-bal"
                         + " (missing opt in by PI sender)!" + state);
                 return allowBasedOnRealCaller(state);
             }
@@ -829,7 +831,8 @@
     }
 
     private BalVerdict abortLaunch(BalState state) {
-        Slog.wtf(TAG, "Background activity launch blocked! " + state);
+        Slog.wtf(TAG, "Background activity launch blocked! goo.gle/android-bal "
+                + state);
         if (balShowToastsBlocked()
                 && (state.mResultForCaller.allows() || state.mResultForRealCaller.allows())) {
             // only show a toast if either caller or real caller could launch if they opted in
@@ -1044,6 +1047,24 @@
                     "realCallingUid has BAL permission.");
         }
 
+        // don't abort if the realCallingUid has SYSTEM_ALERT_WINDOW permission
+        Slog.i(TAG, "hasSystemAlertWindowPermission(" + state.mRealCallingUid + ", "
+                + state.mRealCallingPid + ", " + state.mRealCallingPackage + ") "
+                + balStartModeToString(
+                state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()));
+        if (state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+                == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
+                && mService.hasSystemAlertWindowPermission(state.mRealCallingUid,
+                state.mRealCallingPid, state.mRealCallingPackage)) {
+            Slog.w(
+                    TAG,
+                    "Background activity start for "
+                            + state.mRealCallingPackage
+                            + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
+            return new BalVerdict(BAL_ALLOW_SAW_PERMISSION,
+                    /*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted");
+        }
+
         // if the realCallingUid is a persistent system process, abort if the IntentSender
         // wasn't allowed to start an activity
         if (state.mForcedBalByPiSender.allowsBackgroundActivityStarts()
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 3dc035e..cbe3d79 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -401,6 +401,7 @@
                 || !Objects.equals(first.deviceProductInfo, second.deviceProductInfo)
                 || first.modeId != second.modeId
                 || first.renderFrameRate != second.renderFrameRate
+                || first.hasArrSupport != second.hasArrSupport
                 || first.defaultModeId != second.defaultModeId
                 || first.userPreferredModeId != second.userPreferredModeId
                 || !Arrays.equals(first.supportedModes, second.supportedModes)
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 98919d9..00704b3 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5734,6 +5734,12 @@
     }
 
     private boolean canMoveTaskToBack(Task task) {
+        // Checks whether a task is a child of this task because it can be reparetned when
+        // transition is deferred.
+        if (task != this && task.getParent() != this) {
+            return false;
+        }
+
         // In LockTask mode, moving a locked task to the back of the root task may expose unlocked
         // ones. Therefore we need to check if this operation is allowed.
         if (!mAtmService.getLockTaskController().canMoveTaskToBack(task)) {
@@ -5803,7 +5809,7 @@
                     (deferred) -> {
                         // Need to check again if deferred since the system might
                         // be in a different state.
-                        if (!isAttached() || (deferred && !canMoveTaskToBack(tr))) {
+                        if (!tr.isAttached() || (deferred && !canMoveTaskToBack(tr))) {
                             Slog.e(TAG, "Failed to move task to back after saying we could: "
                                     + tr.mTaskId);
                             transition.abort();
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 d831cf8..b6da3ae 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -51,6 +51,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
@@ -209,8 +210,8 @@
         when(mResourcesMock.getIntArray(
                 com.android.internal.R.array.config_deviceStatesOnWhichToSleep))
                 .thenReturn(new int[]{0});
-        when(mSyntheticModeManagerMock.createAppSupportedModes(any(), any())).thenAnswer(
-                AdditionalAnswers.returnsSecondArg());
+        when(mSyntheticModeManagerMock.createAppSupportedModes(any(), any(), anyBoolean()))
+                .thenAnswer(AdditionalAnswers.returnsSecondArg());
 
         when(mFlagsMock.isConnectedDisplayManagementEnabled()).thenReturn(false);
         mLooper = new TestLooper();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
index 8936f06..b002a1f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
@@ -87,7 +88,7 @@
         mDisplayDeviceInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
                 DISPLAY_WIDTH, DISPLAY_HEIGHT, /* refreshRate= */ 60)};
         when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(mDisplayDeviceInfo);
-        when(mSyntheticModeManager.createAppSupportedModes(any(), any())).thenAnswer(
+        when(mSyntheticModeManager.createAppSupportedModes(any(), any(), anyBoolean())).thenAnswer(
                 AdditionalAnswers.returnsSecondArg());
 
         // Disable binder caches in this process.
@@ -582,7 +583,8 @@
         Display.Mode[] appSupportedModes = new Display.Mode[] {new Display.Mode(OTHER_MODE_ID,
                 DISPLAY_WIDTH, DISPLAY_HEIGHT, /* refreshRate= */ 45)};
         when(mSyntheticModeManager.createAppSupportedModes(
-                any(), eq(mDisplayDeviceInfo.supportedModes))).thenReturn(appSupportedModes);
+                any(), eq(mDisplayDeviceInfo.supportedModes), anyBoolean()))
+                .thenReturn(appSupportedModes);
 
         mLogicalDisplay.updateLocked(mDeviceRepo, mSyntheticModeManager);
         DisplayInfo info = mLogicalDisplay.getDisplayInfoLocked();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SyntheticModeManagerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SyntheticModeManagerTest.kt
index b2d83d7..9a93fba 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SyntheticModeManagerTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SyntheticModeManagerTest.kt
@@ -43,35 +43,42 @@
     @Test
     fun testAppSupportedModes(@TestParameter testCase: AppSupportedModesTestCase) {
         whenever(mockFlags.isSynthetic60HzModesEnabled).thenReturn(testCase.syntheticModesEnabled)
+        whenever(mockFlags.hasArrSupportFlag()).thenReturn(testCase.hasArrSupport)
         whenever(mockConfig.isVrrSupportEnabled).thenReturn(testCase.vrrSupported)
         val syntheticModeManager = SyntheticModeManager(mockFlags)
 
         val result = syntheticModeManager.createAppSupportedModes(
-            mockConfig, testCase.supportedModes)
+            mockConfig, testCase.supportedModes, testCase.hasArrSupport)
 
         assertThat(result).isEqualTo(testCase.expectedAppModes)
     }
 
+    // TODO(b/361433651) Remove vrrSupported once hasArrSupport is rolled out
     enum class AppSupportedModesTestCase(
         val syntheticModesEnabled: Boolean,
         val vrrSupported: Boolean,
+        val hasArrSupport: Boolean,
         val supportedModes: Array<Mode>,
         val expectedAppModes: Array<Mode>
     ) {
-        SYNTHETIC_MODES_NOT_SUPPORTED(false, true, DISPLAY_MODES, DISPLAY_MODES),
-        VRR_NOT_SUPPORTED(true, false, DISPLAY_MODES, DISPLAY_MODES),
-        VRR_SYNTHETIC_NOT_SUPPORTED(false, false, DISPLAY_MODES, DISPLAY_MODES),
-        SINGLE_RESOLUTION_MODES(true, true, DISPLAY_MODES, arrayOf(
+        SYNTHETIC_MODES_NOT_SUPPORTED(false, true, true, DISPLAY_MODES, DISPLAY_MODES),
+        VRR_NOT_SUPPORTED(true, false, false, DISPLAY_MODES, DISPLAY_MODES),
+        VRR_SYNTHETIC_NOT_SUPPORTED(false, false, false, DISPLAY_MODES, DISPLAY_MODES),
+        SINGLE_RESOLUTION_MODES(true, true, true, DISPLAY_MODES, arrayOf(
             Mode(2, 100, 100, 120f),
             Mode(3, 100, 100, 60f, 60f, true, floatArrayOf(), intArrayOf())
         )),
-        NO_60HZ_MODES(true, true, arrayOf(Mode(2, 100, 100, 120f)),
+        SINGLE_RESOLUTION_MODES_HASARR(true, false, true, DISPLAY_MODES, arrayOf(
+            Mode(2, 100, 100, 120f),
+            Mode(3, 100, 100, 60f, 60f, true, floatArrayOf(), intArrayOf())
+        )),
+        NO_60HZ_MODES(true, true, true, arrayOf(Mode(2, 100, 100, 120f)),
             arrayOf(
                 Mode(2, 100, 100, 120f),
                 Mode(3, 100, 100, 60f, 60f, true, floatArrayOf(), intArrayOf())
             )
         ),
-        MULTI_RESOLUTION_MODES(true, true,
+        MULTI_RESOLUTION_MODES(true, true, true,
             arrayOf(
                 Mode(1, 100, 100, 120f),
                 Mode(2, 200, 200, 60f),
@@ -86,7 +93,7 @@
                 Mode(7, 300, 300, 60f, 60f, true, floatArrayOf(), intArrayOf())
             )
         ),
-        WITH_HDR_TYPES(true, true,
+        WITH_HDR_TYPES(true, true, true,
             arrayOf(
                 Mode(1, 100, 100, 120f, 120f, false, floatArrayOf(), intArrayOf(1, 2)),
                 Mode(2, 200, 200, 60f, 120f, false, floatArrayOf(), intArrayOf(3, 4)),
@@ -99,7 +106,7 @@
                 Mode(5, 200, 200, 60f, 60f, true, floatArrayOf(), intArrayOf(5, 6)),
             )
         ),
-        UNACHIEVABLE_60HZ(true, true,
+        UNACHIEVABLE_60HZ(true, true, true,
             arrayOf(
                 Mode(1, 100, 100, 90f),
             ),
@@ -107,7 +114,7 @@
                 Mode(1, 100, 100, 90f),
             )
         ),
-        MULTI_RESOLUTION_MODES_WITH_UNACHIEVABLE_60HZ(true, true,
+        MULTI_RESOLUTION_MODES_WITH_UNACHIEVABLE_60HZ(true, true, true,
             arrayOf(
                 Mode(1, 100, 100, 120f),
                 Mode(2, 200, 200, 90f),
@@ -118,7 +125,7 @@
                 Mode(3, 100, 100, 60f, 60f, true, floatArrayOf(), intArrayOf()),
             )
         ),
-        LOWER_THAN_60HZ_MODES(true, true,
+        LOWER_THAN_60HZ_MODES(true, true, true,
             arrayOf(
                 Mode(1, 100, 100, 30f),
                 Mode(2, 100, 100, 45f),
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index 39acd8d..43a8aa9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -72,7 +72,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.function.Predicate;
 
@@ -486,7 +485,7 @@
             FakeStagedSession session = new FakeStagedSession(239);
             session.setIsApex(true);
             // Call and verify
-            Map<String, ApexInfo> result = mStagingManager.getStagedApexInfos(session);
+            var result = mStagingManager.getStagedApexInfos(session);
             assertThat(result).isEmpty();
         }
         // Invalid session: destroyed
@@ -496,7 +495,7 @@
             session.setIsApex(true);
             session.setDestroyed(true);
             // Call and verify
-            Map<String, ApexInfo> result = mStagingManager.getStagedApexInfos(session);
+            var result = mStagingManager.getStagedApexInfos(session);
             assertThat(result).isEmpty();
         }
     }
@@ -520,8 +519,8 @@
         when(mApexManager.getStagedApexInfos(any())).thenReturn(fakeApexInfos);
 
         // Call and verify
-        Map<String, ApexInfo> result = mStagingManager.getStagedApexInfos(validSession);
-        assertThat(result).containsExactly(fakeApexInfos[0].moduleName, fakeApexInfos[0]);
+        List<ApexInfo> result = mStagingManager.getStagedApexInfos(validSession);
+        assertThat(result).containsExactly(fakeApexInfos[0]);
 
         ArgumentCaptor<ApexSessionParams> argumentCaptor =
                 ArgumentCaptor.forClass(ApexSessionParams.class);
@@ -544,9 +543,8 @@
         when(mApexManager.getStagedApexInfos(any())).thenReturn(fakeApexInfos);
 
         // Call and verify
-        Map<String, ApexInfo> result = mStagingManager.getStagedApexInfos(parentSession);
-        assertThat(result).containsExactly(fakeApexInfos[0].moduleName, fakeApexInfos[0],
-                fakeApexInfos[1].moduleName, fakeApexInfos[1]);
+        List<ApexInfo> result = mStagingManager.getStagedApexInfos(parentSession);
+        assertThat(result).containsExactly(fakeApexInfos[0], fakeApexInfos[1]);
 
         ArgumentCaptor<ApexSessionParams> argumentCaptor =
                 ArgumentCaptor.forClass(ApexSessionParams.class);
@@ -557,7 +555,7 @@
     }
 
     @Test
-    public void getStagedApexModuleNames_returnsStagedApexModules() throws Exception {
+    public void getStagedApexInfos_returnsStagedApexModules() throws Exception {
         FakeStagedSession validSession1 = new FakeStagedSession(239);
         validSession1.setIsApex(true);
         validSession1.setSessionReady();
@@ -575,8 +573,8 @@
 
         mockApexManagerGetStagedApexInfoWithSessionId();
 
-        List<String> result = mStagingManager.getStagedApexModuleNames();
-        assertThat(result).containsExactly("239", "123", "124");
+        List<StagedApexInfo> result = mStagingManager.getStagedApexInfos();
+        assertThat(result).containsExactly((Object[]) fakeStagedApexInfos("239", "123", "124"));
         verify(mApexManager, times(2)).getStagedApexInfos(any());
     }
 
@@ -605,26 +603,12 @@
         });
     }
 
-    @Test
-    public void getStagedApexInfo() throws Exception {
-        FakeStagedSession validSession1 = new FakeStagedSession(239);
-        validSession1.setIsApex(true);
-        validSession1.setSessionReady();
-        mStagingManager.createSession(validSession1);
-        ApexInfo[] fakeApexInfos = getFakeApexInfo(Arrays.asList("module1"));
-        when(mApexManager.getStagedApexInfos(any())).thenReturn(fakeApexInfos);
-
-        // Verify null is returned if module name is not found
-        StagedApexInfo result = mStagingManager.getStagedApexInfo("not found");
-        assertThat(result).isNull();
-        verify(mApexManager, times(1)).getStagedApexInfos(any());
-        // Otherwise, the correct object is returned
-        result = mStagingManager.getStagedApexInfo("module1");
-        assertThat(result.moduleName).isEqualTo(fakeApexInfos[0].moduleName);
-        assertThat(result.diskImagePath).isEqualTo(fakeApexInfos[0].modulePath);
-        assertThat(result.versionCode).isEqualTo(fakeApexInfos[0].versionCode);
-        assertThat(result.versionName).isEqualTo(fakeApexInfos[0].versionName);
-        verify(mApexManager, times(2)).getStagedApexInfos(any());
+    private StagedApexInfo[] fakeStagedApexInfos(String... moduleNames) {
+        return Arrays.stream(moduleNames).map(moduleName -> {
+            StagedApexInfo info = new StagedApexInfo();
+            info.moduleName = moduleName;
+            return info;
+        }).toArray(StagedApexInfo[]::new);
     }
 
     @Test
@@ -646,8 +630,8 @@
             ArgumentCaptor<ApexStagedEvent> argumentCaptor = ArgumentCaptor.forClass(
                     ApexStagedEvent.class);
             verify(observer, times(1)).onApexStaged(argumentCaptor.capture());
-            assertThat(argumentCaptor.getValue().stagedApexModuleNames).isEqualTo(
-                    new String[]{"239"});
+            assertThat(argumentCaptor.getValue().stagedApexInfos).isEqualTo(
+                    fakeStagedApexInfos("239"));
         }
 
         // Create another staged session and verify observers are notified of union
@@ -662,8 +646,8 @@
             ArgumentCaptor<ApexStagedEvent> argumentCaptor = ArgumentCaptor.forClass(
                     ApexStagedEvent.class);
             verify(observer, times(1)).onApexStaged(argumentCaptor.capture());
-            assertThat(argumentCaptor.getValue().stagedApexModuleNames).isEqualTo(
-                    new String[]{"239", "240"});
+            assertThat(argumentCaptor.getValue().stagedApexInfos).isEqualTo(
+                    fakeStagedApexInfos("239", "240"));
         }
 
         // Finally, verify that once unregistered, observer is not notified
@@ -699,7 +683,7 @@
         ArgumentCaptor<ApexStagedEvent> argumentCaptor = ArgumentCaptor.forClass(
                 ApexStagedEvent.class);
         verify(observer, times(1)).onApexStaged(argumentCaptor.capture());
-        assertThat(argumentCaptor.getValue().stagedApexModuleNames).hasLength(0);
+        assertThat(argumentCaptor.getValue().stagedApexInfos).hasLength(0);
     }
 
     @Test
diff --git a/services/tests/security/forensic/OWNERS b/services/tests/security/forensic/OWNERS
new file mode 100644
index 0000000..80c9afb9
--- /dev/null
+++ b/services/tests/security/forensic/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 36824
+
+file:platform/frameworks/base:main:/core/java/android/security/forensic/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
index 3e2949d6..de5564c 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
@@ -20,6 +20,8 @@
 import static com.android.media.audio.Flags.FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME;
 import static com.android.media.audio.Flags.absVolumeIndexFix;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
@@ -109,12 +111,13 @@
         mAudioService.setDeviceVolume(volMin, usbDevice, mPackageName);
         mTestLooper.dispatchAll();
         verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
-                        AudioManager.STREAM_MUSIC, minIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
+                eq(AudioManager.STREAM_MUSIC), eq(minIndex), anyBoolean(),
+                eq(AudioSystem.DEVICE_OUT_USB_DEVICE));
 
         mAudioService.setDeviceVolume(volMid, usbDevice, mPackageName);
         mTestLooper.dispatchAll();
         verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
-                AudioManager.STREAM_MUSIC, midIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
+                AudioManager.STREAM_MUSIC, midIndex, false, AudioSystem.DEVICE_OUT_USB_DEVICE);
     }
 
     @Test
@@ -151,7 +154,7 @@
 
             // Stream volume changes
             verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
-                            AudioManager.STREAM_MUSIC, targetIndex,
+                            AudioManager.STREAM_MUSIC, targetIndex, false,
                             AudioSystem.DEVICE_OUT_BLE_HEADSET);
         }
 
@@ -162,7 +165,7 @@
         mTestLooper.dispatchAll();
 
         verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
-                        AudioManager.STREAM_MUSIC, maxIndex,
+                        AudioManager.STREAM_MUSIC, maxIndex, false,
                         AudioSystem.DEVICE_OUT_BLE_HEADSET);
     }
 
@@ -193,8 +196,8 @@
             }
             // Stream volume changes
             verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
-                            AudioManager.STREAM_MUSIC, passedIndex,
-                            AudioSystem.DEVICE_OUT_BLE_HEADSET);
+                    AudioManager.STREAM_MUSIC, passedIndex, false,
+                    AudioSystem.DEVICE_OUT_BLE_HEADSET);
         }
 
         // Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:4)
@@ -207,7 +210,7 @@
             passedIndex = 4;
         }
         verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
-                        AudioManager.STREAM_MUSIC, passedIndex,
-                        AudioSystem.DEVICE_OUT_BLE_HEADSET);
+                AudioManager.STREAM_MUSIC, passedIndex, false,
+                AudioSystem.DEVICE_OUT_BLE_HEADSET);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
index 96ac5d2..ce59a86 100644
--- a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
+++ b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
@@ -132,12 +132,13 @@
     }
 
     @Override
-    public int setStreamVolumeIndexAS(int stream, int index, int device) {
+    public int setStreamVolumeIndexAS(int stream, int index, boolean muted, int device) {
         return AudioSystem.AUDIO_STATUS_OK;
     }
 
     @Override
-    public int setVolumeIndexForAttributes(AudioAttributes attributes, int index, int device) {
+    public int setVolumeIndexForAttributes(AudioAttributes attributes, int index, boolean muted,
+            int device) {
         return AudioSystem.AUDIO_STATUS_OK;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
index dc8c1b9..6b41c43 100644
--- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
@@ -18,6 +18,7 @@
 import static android.media.AudioManager.ADJUST_LOWER;
 import static android.media.AudioManager.ADJUST_MUTE;
 import static android.media.AudioManager.ADJUST_RAISE;
+import static android.media.AudioManager.ADJUST_UNMUTE;
 import static android.media.AudioManager.DEVICE_OUT_BLE_SPEAKER;
 import static android.media.AudioManager.DEVICE_OUT_BLUETOOTH_SCO;
 import static android.media.AudioManager.DEVICE_OUT_SPEAKER;
@@ -41,13 +42,13 @@
 
 import static com.android.media.audio.Flags.FLAG_ABS_VOLUME_INDEX_FIX;
 import static com.android.media.audio.Flags.FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME;
+import static com.android.media.audio.Flags.FLAG_RING_MY_CAR;
 import static com.android.media.audio.Flags.absVolumeIndexFix;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
@@ -180,6 +181,10 @@
             }
             return mStreamDevice.get(stream);
         }
+
+        public void setMuteAffectedStreams(int muteAffectedStreams) {
+            mMuteAffectedStreams = muteAffectedStreams;
+        }
     }
 
     private static class TestDeviceVolumeBehaviorDispatcherStub
@@ -223,6 +228,7 @@
                 mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy,
                 mTestLooper.getLooper(), mMockAppOpsManager, mMockPermissionEnforcer,
                 mMockPermissionProvider);
+        mAudioService.setMuteAffectedStreams(AudioSystem.DEFAULT_MUTE_STREAMS_AFFECTED);
 
         mTestLooper.dispatchAll();
         prepareAudioServiceState();
@@ -258,6 +264,8 @@
         for (int streamType : usedStreamTypes) {
             mAudioService.setStreamVolume(streamType, DEFAULT_STREAM_VOLUME, /*flags=*/0,
                     mContext.getOpPackageName());
+            mAudioService.adjustStreamVolume(streamType, ADJUST_UNMUTE, /*flags=*/0,
+                    mContext.getOpPackageName());
         }
 
         if (!mIsAutomotive) {
@@ -301,7 +309,20 @@
         mTestLooper.dispatchAll();
 
         verify(mSpyAudioSystem).setStreamVolumeIndexAS(
-                eq(STREAM_MUSIC), eq(newIndex), eq(DEVICE_OUT_USB_DEVICE));
+                STREAM_MUSIC, newIndex, /*muted=*/false, DEVICE_OUT_USB_DEVICE);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_RING_MY_CAR)
+    public void adjustStreamVolume_adjustMute_callsASSetStreamVolumeIndex() throws Exception {
+        int currentIndex = mAudioService.getStreamVolume(STREAM_MUSIC);
+
+        mAudioService.adjustStreamVolume(STREAM_MUSIC, ADJUST_MUTE, /*flags=*/0,
+                mContext.getOpPackageName());
+        mTestLooper.dispatchAll();
+
+        verify(mSpyAudioSystem).setStreamVolumeIndexAS(
+                eq(STREAM_MUSIC), eq(currentIndex), /*muted=*/eq(true), anyInt());
     }
 
     @Test
@@ -325,7 +346,7 @@
         mTestLooper.dispatchAll();
 
         verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
-                eq(STREAM_MUSIC), anyInt(), eq(DEVICE_OUT_USB_DEVICE));
+                eq(STREAM_MUSIC), anyInt(), anyBoolean(), eq(DEVICE_OUT_USB_DEVICE));
     }
 
     @Test
@@ -341,7 +362,7 @@
         mTestLooper.dispatchAll();
 
         verify(mSpyAudioSystem).setStreamVolumeIndexAS(
-                eq(STREAM_MUSIC), anyInt(), eq(DEVICE_OUT_USB_DEVICE));
+                eq(STREAM_MUSIC), anyInt(), eq(false), eq(DEVICE_OUT_USB_DEVICE));
     }
 
     // --------------- Volume Group APIs ---------------
@@ -356,15 +377,15 @@
         mAudioService.setDeviceForStream(STREAM_MUSIC, DEVICE_OUT_USB_DEVICE);
         mAudioService.setVolumeGroupVolumeIndex(mAudioMusicVolumeGroup.getId(),
                 circularNoMinMaxIncrementVolume(STREAM_MUSIC), /*flags=*/0,
-                mContext.getOpPackageName(),  /*attributionTag*/null);
+                mContext.getOpPackageName(), /*attributionTag*/null);
         mTestLooper.dispatchAll();
 
-        verify(mSpyAudioSystem).setVolumeIndexForAttributes(any(), anyInt(),
+        verify(mSpyAudioSystem).setVolumeIndexForAttributes(any(), anyInt(), eq(false),
                 eq(DEVICE_OUT_USB_DEVICE));
     }
 
     @Test
-    public void adjustVolumeGroupVolume_callsASSetVolumeIndexForAttributes() throws Exception {
+    public void adjustVolumeGroupVolume_callsASSetStreamVolumeIndexAS() throws Exception {
         assumeNotNull(mAudioMusicVolumeGroup);
 
         mAudioService.setDeviceForStream(STREAM_MUSIC, DEVICE_OUT_USB_DEVICE);
@@ -372,8 +393,24 @@
                 ADJUST_LOWER, /*flags=*/0, mContext.getOpPackageName());
         mTestLooper.dispatchAll();
 
-        verify(mSpyAudioSystem).setVolumeIndexForAttributes(
-                any(), anyInt(), eq(DEVICE_OUT_USB_DEVICE));
+        // adjust calls setStreamVolumeIndexAS instead of setVolumeIndexForAttributes
+        verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
+                anyInt(), anyInt(), anyBoolean(), eq(DEVICE_OUT_USB_DEVICE));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_RING_MY_CAR)
+    public void adjustVolumeGroupVolume_adjustMute_callsASSetStreamVolumeIndexAS()
+            throws Exception {
+        assumeNotNull(mAudioMusicVolumeGroup);
+
+        mAudioService.adjustVolumeGroupVolume(mAudioMusicVolumeGroup.getId(),
+                ADJUST_MUTE, /*flags=*/0, mContext.getOpPackageName());
+        mTestLooper.dispatchAll();
+
+        // adjust calls setStreamVolumeIndexAS instead of setVolumeIndexForAttributes
+        verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
+                anyInt(), anyInt(), eq(true), anyInt());
     }
 
     @Test
@@ -437,7 +474,7 @@
 
     @Test
     public void check_isStreamAffectedByMute() {
-        assertFalse(mAudioService.isStreamAffectedByMute(STREAM_VOICE_CALL));
+        assertTrue(mAudioService.isStreamAffectedByMute(STREAM_VOICE_CALL));
     }
 
     // --------------------- Volume Flag Check --------------------
@@ -452,14 +489,14 @@
                 mContext.getOpPackageName());
         mTestLooper.dispatchAll();
         verify(mSpyAudioSystem).setStreamVolumeIndexAS(
-                eq(STREAM_NOTIFICATION), anyInt(), eq(DEVICE_OUT_BLE_SPEAKER));
+                eq(STREAM_NOTIFICATION), anyInt(), eq(false), eq(DEVICE_OUT_BLE_SPEAKER));
 
         reset(mSpyAudioSystem);
         mAudioService.adjustStreamVolume(STREAM_NOTIFICATION, ADJUST_LOWER,
                 FLAG_BLUETOOTH_ABS_VOLUME, mContext.getOpPackageName());
         mTestLooper.dispatchAll();
         verify(mSpyAudioSystem).setStreamVolumeIndexAS(
-                eq(STREAM_NOTIFICATION), anyInt(), eq(DEVICE_OUT_BLE_SPEAKER));
+                eq(STREAM_NOTIFICATION), anyInt(), eq(false), eq(DEVICE_OUT_BLE_SPEAKER));
     }
 
     @Test
@@ -471,13 +508,13 @@
                 mContext.getOpPackageName());
         mTestLooper.dispatchAll();
         verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS(
-                eq(STREAM_NOTIFICATION), eq(newIndex), eq(DEVICE_OUT_BLE_SPEAKER));
+                eq(STREAM_NOTIFICATION), eq(newIndex), eq(false), eq(DEVICE_OUT_BLE_SPEAKER));
 
         mAudioService.adjustStreamVolume(STREAM_NOTIFICATION, ADJUST_LOWER,
                 FLAG_BLUETOOTH_ABS_VOLUME, mContext.getOpPackageName());
         mTestLooper.dispatchAll();
         verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS(
-                eq(STREAM_NOTIFICATION), anyInt(), eq(DEVICE_OUT_BLE_SPEAKER));
+                eq(STREAM_NOTIFICATION), anyInt(), eq(false), eq(DEVICE_OUT_BLE_SPEAKER));
     }
 
     @Test
@@ -523,7 +560,7 @@
         mTestLooper.dispatchAll();
 
         verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS(
-                eq(STREAM_MUSIC), anyInt(), anyInt());
+                eq(STREAM_MUSIC), anyInt(), eq(false), anyInt());
     }
 
     @Test
@@ -537,7 +574,7 @@
         mTestLooper.dispatchAll();
 
         verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS(
-                eq(STREAM_VOICE_CALL), anyInt(), eq(DEVICE_OUT_USB_DEVICE));
+                eq(STREAM_VOICE_CALL), anyInt(), eq(false), eq(DEVICE_OUT_USB_DEVICE));
 
         mAudioService.setDeviceForStream(STREAM_BLUETOOTH_SCO, DEVICE_OUT_BLUETOOTH_SCO);
         mAudioService.adjustStreamVolume(STREAM_BLUETOOTH_SCO, ADJUST_MUTE, /*flags=*/0,
@@ -545,7 +582,7 @@
         mTestLooper.dispatchAll();
 
         verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS(
-                eq(STREAM_BLUETOOTH_SCO), anyInt(), eq(DEVICE_OUT_USB_DEVICE));
+                eq(STREAM_BLUETOOTH_SCO), anyInt(), eq(false), eq(DEVICE_OUT_USB_DEVICE));
     }
 
     // ----------------- AudioDeviceVolumeManager -----------------
@@ -568,18 +605,18 @@
         mTestLooper.dispatchAll();
 
         // there is a min/max index mismatch in automotive
-        assertEquals(volMin, mAudioService.getDeviceVolume(volMin, usbDevice,
-                mContext.getOpPackageName()));
+        assertEquals(volMin.getVolumeIndex(), mAudioService.getDeviceVolume(volMin, usbDevice,
+                mContext.getOpPackageName()).getVolumeIndex());
         verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
-                eq(STREAM_MUSIC), anyInt(), eq(AudioSystem.DEVICE_OUT_USB_DEVICE));
+                eq(STREAM_MUSIC), anyInt(), anyBoolean(), eq(AudioSystem.DEVICE_OUT_USB_DEVICE));
 
         mAudioService.setDeviceVolume(volMid, usbDevice, mContext.getOpPackageName());
         mTestLooper.dispatchAll();
         // there is a min/max index mismatch in automotive
-        assertEquals(volMid, mAudioService.getDeviceVolume(volMid, usbDevice,
-                mContext.getOpPackageName()));
+        assertEquals(volMid.getVolumeIndex(), mAudioService.getDeviceVolume(volMid, usbDevice,
+                mContext.getOpPackageName()).getVolumeIndex());
         verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
-                eq(STREAM_MUSIC), anyInt(), eq(AudioSystem.DEVICE_OUT_USB_DEVICE));
+                eq(STREAM_MUSIC), anyInt(), anyBoolean(), eq(AudioSystem.DEVICE_OUT_USB_DEVICE));
     }
 
     @Test
@@ -617,8 +654,7 @@
                     mAudioService.getDeviceVolume(volCur, bleDevice, mContext.getOpPackageName()));
             // Stream volume changes
             verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
-                    STREAM_MUSIC, targetIndex,
-                    AudioSystem.DEVICE_OUT_BLE_HEADSET);
+                    STREAM_MUSIC, targetIndex, false, AudioSystem.DEVICE_OUT_BLE_HEADSET);
         }
 
         // Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:4)
@@ -630,8 +666,7 @@
         assertEquals(volIndex4,
                 mAudioService.getDeviceVolume(volIndex4, bleDevice, mContext.getOpPackageName()));
         verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
-                STREAM_MUSIC, maxIndex,
-                AudioSystem.DEVICE_OUT_BLE_HEADSET);
+                STREAM_MUSIC, maxIndex, false, AudioSystem.DEVICE_OUT_BLE_HEADSET);
     }
 
     @Test
@@ -660,8 +695,7 @@
             }
             // Stream volume changes
             verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
-                    STREAM_MUSIC, passedIndex,
-                    AudioSystem.DEVICE_OUT_BLE_HEADSET);
+                    STREAM_MUSIC, passedIndex, false, AudioSystem.DEVICE_OUT_BLE_HEADSET);
         }
 
         // Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:4)
@@ -674,8 +708,7 @@
             passedIndex = 4;
         }
         verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
-                STREAM_MUSIC, passedIndex,
-                AudioSystem.DEVICE_OUT_BLE_HEADSET);
+                STREAM_MUSIC, passedIndex, false, AudioSystem.DEVICE_OUT_BLE_HEADSET);
     }
 
     // ---------------- DeviceVolumeBehaviorTest ----------------
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index eb9cce0..d1f6c2f 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -69,7 +69,6 @@
 import com.android.internal.R;
 import com.android.internal.pm.parsing.PackageParser2;
 import com.android.server.compat.PlatformCompat;
-import com.android.server.integrity.engine.RuleEvaluationEngine;
 import com.android.server.integrity.model.IntegrityCheckResult;
 import com.android.server.pm.parsing.TestPackageParser2;
 import com.android.server.testutils.TestUtils;
@@ -138,7 +137,6 @@
     @Mock PlatformCompat mPlatformCompat;
     @Mock Context mMockContext;
     @Mock Resources mMockResources;
-    @Mock RuleEvaluationEngine mRuleEvaluationEngine;
     @Mock IntegrityFileManager mIntegrityFileManager;
     @Mock Handler mHandler;
 
@@ -176,7 +174,6 @@
                         mMockContext,
                         mPackageManagerInternal,
                         mParserSupplier,
-                        mRuleEvaluationEngine,
                         mIntegrityFileManager,
                         mHandler);
 
@@ -307,91 +304,6 @@
     }
 
     @Test
-    public void handleBroadcast_correctArgs() throws Exception {
-        allowlistUsAsRuleProvider();
-        makeUsSystemApp();
-        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(mMockContext)
-                .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
-        Intent intent = makeVerificationIntent();
-        when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
-
-        broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
-        runJobInHandler();
-
-        ArgumentCaptor<AppInstallMetadata> metadataCaptor =
-                ArgumentCaptor.forClass(AppInstallMetadata.class);
-        verify(mRuleEvaluationEngine).evaluate(metadataCaptor.capture());
-        AppInstallMetadata appInstallMetadata = metadataCaptor.getValue();
-        assertEquals(PACKAGE_NAME, appInstallMetadata.getPackageName());
-        assertThat(appInstallMetadata.getAppCertificates()).containsExactly(APP_CERT);
-        assertEquals(INSTALLER_SHA256, appInstallMetadata.getInstallerName());
-        // we cannot check installer cert because it seems to be device specific.
-        assertEquals(VERSION_CODE, appInstallMetadata.getVersionCode());
-        assertFalse(appInstallMetadata.isPreInstalled());
-        // Asserting source stamp not present.
-        assertFalse(appInstallMetadata.isStampPresent());
-        assertFalse(appInstallMetadata.isStampVerified());
-        assertFalse(appInstallMetadata.isStampTrusted());
-        assertNull(appInstallMetadata.getStampCertificateHash());
-        // These are hardcoded in the test apk android manifest
-        Map<String, String> allowedInstallers =
-                appInstallMetadata.getAllowedInstallersAndCertificates();
-        assertEquals(2, allowedInstallers.size());
-        assertEquals(PLAY_STORE_CERT, allowedInstallers.get(PLAY_STORE_PKG));
-        assertEquals(INSTALLER_CERTIFICATE_NOT_EVALUATED, allowedInstallers.get(ADB_INSTALLER));
-    }
-
-    @Test
-    public void handleBroadcast_correctArgs_multipleCerts() throws Exception {
-        allowlistUsAsRuleProvider();
-        makeUsSystemApp();
-        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(mMockContext)
-                .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
-        Intent intent = makeVerificationIntent();
-        intent.setDataAndType(Uri.fromFile(mTestApkTwoCerts), PACKAGE_MIME_TYPE);
-        when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
-
-        broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
-        runJobInHandler();
-
-        ArgumentCaptor<AppInstallMetadata> metadataCaptor =
-                ArgumentCaptor.forClass(AppInstallMetadata.class);
-        verify(mRuleEvaluationEngine).evaluate(metadataCaptor.capture());
-        AppInstallMetadata appInstallMetadata = metadataCaptor.getValue();
-        assertThat(appInstallMetadata.getAppCertificates())
-                .containsExactly(DUMMY_APP_TWO_CERTS_CERT_1, DUMMY_APP_TWO_CERTS_CERT_2);
-    }
-
-    @Test
-    public void handleBroadcast_correctArgs_sourceStamp() throws Exception {
-        allowlistUsAsRuleProvider();
-        makeUsSystemApp();
-        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(mMockContext)
-                .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
-        Intent intent = makeVerificationIntent();
-        intent.setDataAndType(Uri.fromFile(mTestApkSourceStamp), PACKAGE_MIME_TYPE);
-        when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
-
-        broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
-        runJobInHandler();
-
-        ArgumentCaptor<AppInstallMetadata> metadataCaptor =
-                ArgumentCaptor.forClass(AppInstallMetadata.class);
-        verify(mRuleEvaluationEngine).evaluate(metadataCaptor.capture());
-        AppInstallMetadata appInstallMetadata = metadataCaptor.getValue();
-        assertTrue(appInstallMetadata.isStampPresent());
-        assertTrue(appInstallMetadata.isStampVerified());
-        assertTrue(appInstallMetadata.isStampTrusted());
-        assertEquals(SOURCE_STAMP_CERTIFICATE_HASH, appInstallMetadata.getStampCertificateHash());
-    }
-
-    @Test
     public void handleBroadcast_allow() throws Exception {
         allowlistUsAsRuleProvider();
         makeUsSystemApp();
@@ -400,7 +312,6 @@
         verify(mMockContext)
                 .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
         Intent intent = makeVerificationIntent();
-        when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
 
         broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
         runJobInHandler();
@@ -411,32 +322,6 @@
     }
 
     @Test
-    public void handleBroadcast_reject() throws Exception {
-        allowlistUsAsRuleProvider();
-        makeUsSystemApp();
-        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(mMockContext)
-                .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
-        when(mRuleEvaluationEngine.evaluate(any()))
-                .thenReturn(
-                        IntegrityCheckResult.deny(
-                                Arrays.asList(
-                                        new Rule(
-                                                new AtomicFormula.BooleanAtomicFormula(
-                                                        AtomicFormula.PRE_INSTALLED, false),
-                                                Rule.DENY))));
-        Intent intent = makeVerificationIntent();
-
-        broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
-        runJobInHandler();
-
-        verify(mPackageManagerInternal)
-                .setIntegrityVerificationResult(
-                        1, PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT);
-    }
-
-    @Test
     public void handleBroadcast_notInitialized() throws Exception {
         allowlistUsAsRuleProvider();
         makeUsSystemApp();
@@ -446,7 +331,6 @@
         verify(mMockContext)
                 .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
         Intent intent = makeVerificationIntent();
-        when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
 
         broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
         runJobInHandler();
@@ -467,8 +351,6 @@
         verify(mMockContext, atLeastOnce())
                 .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
         Intent intent = makeVerificationIntent(TEST_FRAMEWORK_PACKAGE);
-        when(mRuleEvaluationEngine.evaluate(any()))
-                .thenReturn(IntegrityCheckResult.deny(/* rule= */ null));
 
         broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
         runJobInHandler();
diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
deleted file mode 100644
index e1ee9c3..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * 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.server.integrity.engine;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.when;
-
-import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.Rule;
-
-import com.android.server.integrity.IntegrityFileManager;
-import com.android.server.integrity.model.IntegrityCheckResult;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-@RunWith(JUnit4.class)
-public class RuleEvaluationEngineTest {
-
-    private static final String INSTALLER_1 = "installer1";
-    private static final String INSTALLER_1_CERT = "installer1_cert";
-    private static final String INSTALLER_2 = "installer2";
-    private static final String INSTALLER_2_CERT = "installer2_cert";
-
-    private static final String RANDOM_INSTALLER = "random";
-    private static final String RANDOM_INSTALLER_CERT = "random_cert";
-
-    @Mock
-    private IntegrityFileManager mIntegrityFileManager;
-
-    private RuleEvaluationEngine mEngine;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        mEngine = new RuleEvaluationEngine(mIntegrityFileManager);
-
-        when(mIntegrityFileManager.readRules(any())).thenReturn(Collections.singletonList(new Rule(
-                IntegrityFormula.Installer.notAllowedByManifest(), Rule.DENY)));
-
-        when(mIntegrityFileManager.initialized()).thenReturn(true);
-    }
-
-    @Test
-    public void testAllowedInstallers_empty() {
-        AppInstallMetadata appInstallMetadata1 =
-                getAppInstallMetadataBuilder()
-                        .setInstallerName(INSTALLER_1)
-                        .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
-                        .build();
-        AppInstallMetadata appInstallMetadata2 =
-                getAppInstallMetadataBuilder()
-                        .setInstallerName(INSTALLER_2)
-                        .setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT))
-                        .build();
-        AppInstallMetadata appInstallMetadata3 =
-                getAppInstallMetadataBuilder()
-                        .setInstallerName(RANDOM_INSTALLER)
-                        .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
-                        .build();
-
-        assertThat(mEngine.evaluate(appInstallMetadata1).getEffect())
-                .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-        assertThat(mEngine.evaluate(appInstallMetadata2).getEffect())
-                .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-        assertThat(mEngine.evaluate(appInstallMetadata3).getEffect())
-                .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-    }
-
-    @Test
-    public void testAllowedInstallers_oneElement() {
-        Map<String, String> allowedInstallers =
-                Collections.singletonMap(INSTALLER_1, INSTALLER_1_CERT);
-
-        AppInstallMetadata appInstallMetadata1 =
-                getAppInstallMetadataBuilder()
-                        .setInstallerName(INSTALLER_1)
-                        .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
-                        .setAllowedInstallersAndCert(allowedInstallers)
-                        .build();
-        assertThat(mEngine.evaluate(appInstallMetadata1).getEffect())
-                .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-
-        AppInstallMetadata appInstallMetadata2 =
-                getAppInstallMetadataBuilder()
-                        .setInstallerName(RANDOM_INSTALLER)
-                        .setAllowedInstallersAndCert(allowedInstallers)
-                        .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
-                        .build();
-        assertThat(mEngine.evaluate(appInstallMetadata2).getEffect())
-                .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-
-        AppInstallMetadata appInstallMetadata3 =
-                getAppInstallMetadataBuilder()
-                        .setInstallerName(INSTALLER_1)
-                        .setAllowedInstallersAndCert(allowedInstallers)
-                        .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
-                        .build();
-        assertThat(mEngine.evaluate(appInstallMetadata3).getEffect())
-                .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-
-        AppInstallMetadata appInstallMetadata4 =
-                getAppInstallMetadataBuilder()
-                        .setInstallerName(INSTALLER_1)
-                        .setAllowedInstallersAndCert(allowedInstallers)
-                        .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
-                        .build();
-        assertThat(mEngine.evaluate(appInstallMetadata4).getEffect())
-                .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-    }
-
-    @Test
-    public void testAllowedInstallers_multipleElement() {
-        Map<String, String> allowedInstallers = new HashMap<>(2);
-        allowedInstallers.put(INSTALLER_1, INSTALLER_1_CERT);
-        allowedInstallers.put(INSTALLER_2, INSTALLER_2_CERT);
-
-        AppInstallMetadata appInstallMetadata1 =
-                getAppInstallMetadataBuilder()
-                        .setInstallerName(INSTALLER_1)
-                        .setAllowedInstallersAndCert(allowedInstallers)
-                        .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
-                        .build();
-        assertThat(mEngine.evaluate(appInstallMetadata1).getEffect())
-                .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-
-        AppInstallMetadata appInstallMetadata2 =
-                getAppInstallMetadataBuilder()
-                        .setInstallerName(INSTALLER_2)
-                        .setAllowedInstallersAndCert(allowedInstallers)
-                        .setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT))
-                        .build();
-        assertThat(mEngine.evaluate(appInstallMetadata2).getEffect())
-                .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-
-        AppInstallMetadata appInstallMetadata3 =
-                getAppInstallMetadataBuilder()
-                        .setInstallerName(INSTALLER_1)
-                        .setAllowedInstallersAndCert(allowedInstallers)
-                        .setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT))
-                        .build();
-        assertThat(mEngine.evaluate(appInstallMetadata3).getEffect())
-                .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-
-        AppInstallMetadata appInstallMetadata4 =
-                getAppInstallMetadataBuilder()
-                        .setInstallerName(INSTALLER_2)
-                        .setAllowedInstallersAndCert(allowedInstallers)
-                        .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
-                        .build();
-        assertThat(mEngine.evaluate(appInstallMetadata4).getEffect())
-                .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-    }
-
-    /** Returns a builder with all fields filled with some placeholder data. */
-    private AppInstallMetadata.Builder getAppInstallMetadataBuilder() {
-        return new AppInstallMetadata.Builder()
-                .setPackageName("abc")
-                .setAppCertificates(Collections.singletonList("abc"))
-                .setAppCertificateLineage(Collections.singletonList("abc"))
-                .setInstallerCertificates(Collections.singletonList("abc"))
-                .setInstallerName("abc")
-                .setVersionCode(-1)
-                .setIsPreInstalled(true);
-    }
-}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java
index 4af96ef..05210ac 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java
@@ -19,13 +19,25 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
 import android.app.Application;
 import android.app.PendingIntent;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 
@@ -35,16 +47,24 @@
 import com.android.server.pm.PackageManagerService;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+
+import java.util.List;
 
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 @RunWithLooper
 public class EventConditionProviderTest extends UiServiceTestCase {
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
-    EventConditionProvider mService;
+    private EventConditionProvider mService;
+    @Mock private UserManager mUserManager;
 
     @Before
     public void setUp() throws Exception {
@@ -65,6 +85,18 @@
         service.onCreate();
         service.onBind(startIntent);
         mService = spy(service);
+        mService.mContext = this.getContext();
+
+        mContext.addMockSystemService(UserManager.class, mUserManager);
+        when(mUserManager.getProfiles(eq(mUserId))).thenReturn(
+                List.of(new UserInfo(mUserId, "mUserId", 0)));
+
+        doAnswer((Answer<Context>) invocationOnMock -> {
+            Context mockUserContext = mock(Context.class);
+            UserHandle userHandle = invocationOnMock.getArgument(2);
+            when(mockUserContext.getUserId()).thenReturn(userHandle.getIdentifier());
+            return mockUserContext;
+        }).when(mContext).createPackageContextAsUser(any(), anyInt(), any());
     }
 
     @Test
@@ -78,4 +110,52 @@
         PendingIntent pi = mService.getPendingIntent(1000);
         assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME, pi.getIntent().getPackage());
     }
+
+    @Test
+    @DisableFlags(android.app.Flags.FLAG_MODES_HSUM)
+    public void onBootComplete_flagOff_loadsTrackers() {
+        when(mUserManager.getUserProfiles()).thenReturn(List.of(UserHandle.of(mUserId)));
+
+        mService.onBootComplete();
+
+        assertThat(mService.getTrackers().size()).isEqualTo(1);
+        assertThat(mService.getTrackers().keyAt(0)).isEqualTo(mUserId);
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_HSUM)
+    public void onBootComplete_waitsForUserSwitched() {
+        mService.onBootComplete();
+        assertThat(mService.getTrackers().size()).isEqualTo(0);
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_HSUM)
+    public void onUserSwitched_reloadsTrackers() {
+        UserHandle someUser = UserHandle.of(42);
+        when(mUserManager.getProfiles(eq(42))).thenReturn(List.of(new UserInfo(42, "user 42", 0)));
+
+        mService.onUserSwitched(someUser);
+
+        assertThat(mService.getTrackers().size()).isEqualTo(1);
+        assertThat(mService.getTrackers().keyAt(0)).isEqualTo(42);
+        assertThat(mService.getTrackers().valueAt(0).getUserId()).isEqualTo(42);
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_HSUM)
+    public void onUserSwitched_reloadsTrackersIncludingProfiles() {
+        UserHandle anotherUser = UserHandle.of(42);
+        when(mUserManager.getProfiles(eq(42))).thenReturn(List.of(
+                new UserInfo(42, "user 42", 0),
+                new UserInfo(43, "profile 43", 0)));
+
+        mService.onUserSwitched(anotherUser);
+
+        assertThat(mService.getTrackers().size()).isEqualTo(2);
+        assertThat(mService.getTrackers().keyAt(0)).isEqualTo(42);
+        assertThat(mService.getTrackers().valueAt(0).getUserId()).isEqualTo(42);
+        assertThat(mService.getTrackers().keyAt(1)).isEqualTo(43);
+        assertThat(mService.getTrackers().valueAt(1).getUserId()).isEqualTo(43);
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index eef3b25..0f7de7d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -16,6 +16,9 @@
 package com.android.server.notification;
 
 import static android.os.UserHandle.USER_ALL;
+import static android.service.notification.Adjustment.KEY_IMPORTANCE;
+
+import static com.android.server.notification.NotificationManagerService.DEFAULT_ALLOWED_ADJUSTMENTS;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -631,4 +634,50 @@
 
         assertThat(mAssistants.getUnsupportedAdjustments(userId).size()).isEqualTo(0);
     }
+
+    @Test
+    @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public void testDisallowAdjustmentType() {
+        mAssistants.disallowAdjustmentType(Adjustment.KEY_RANKING_SCORE);
+        assertThat(mAssistants.getAllowedAssistantAdjustments())
+                .doesNotContain(Adjustment.KEY_RANKING_SCORE);
+        assertThat(mAssistants.getAllowedAssistantAdjustments()).contains(Adjustment.KEY_TYPE);
+    }
+
+    @Test
+    @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public void testAllowAdjustmentType() {
+        mAssistants.disallowAdjustmentType(Adjustment.KEY_RANKING_SCORE);
+        assertThat(mAssistants.getAllowedAssistantAdjustments())
+                .doesNotContain(Adjustment.KEY_RANKING_SCORE);
+        mAssistants.allowAdjustmentType(Adjustment.KEY_RANKING_SCORE);
+        assertThat(mAssistants.getAllowedAssistantAdjustments())
+                .contains(Adjustment.KEY_RANKING_SCORE);
+    }
+
+    @Test
+    @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public void testDisallowAdjustmentType_readWriteXml_entries() throws Exception {
+        int userId = ActivityManager.getCurrentUser();
+
+        mAssistants.loadDefaultsFromConfig(true);
+        mAssistants.disallowAdjustmentType(KEY_IMPORTANCE);
+
+        writeXmlAndReload(USER_ALL);
+
+        assertThat(mAssistants.getAllowedAssistantAdjustments()).contains(
+                Adjustment.KEY_NOT_CONVERSATION);
+        assertThat(mAssistants.getAllowedAssistantAdjustments()).doesNotContain(
+                KEY_IMPORTANCE);
+    }
+
+    @Test
+    public void testDefaultAllowedAdjustments_readWriteXml_entries() throws Exception {
+        mAssistants.loadDefaultsFromConfig(true);
+
+        writeXmlAndReload(USER_ALL);
+
+        assertThat(mAssistants.getAllowedAssistantAdjustments())
+                .containsExactlyElementsIn(DEFAULT_ALLOWED_ADJUSTMENTS);
+    }
 }
\ 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 1349ee0..becb9fd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -201,6 +201,7 @@
 import android.app.RemoteInputHistoryItem;
 import android.app.StatsManager;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.backup.BackupRestoreEventLogger;
 import android.app.job.JobScheduler;
 import android.app.role.RoleManager;
 import android.app.usage.UsageStatsManagerInternal;
@@ -6340,7 +6341,7 @@
         mService.readPolicyXml(
                 new BufferedInputStream(new ByteArrayInputStream(upgradeXml.getBytes())),
                 false,
-                UserHandle.USER_ALL);
+                UserHandle.USER_ALL, null);
         verify(mListeners, times(1)).readXml(any(), any(), anyBoolean(), anyInt());
         verify(mConditionProviders, times(1)).readXml(any(), any(), anyBoolean(), anyInt());
         verify(mAssistants, times(1)).readXml(any(), any(), anyBoolean(), anyInt());
@@ -6360,7 +6361,7 @@
         mService.readPolicyXml(
                 new BufferedInputStream(new ByteArrayInputStream(upgradeXml.getBytes())),
                 false,
-                UserHandle.USER_ALL);
+                UserHandle.USER_ALL, null);
         verify(mSnoozeHelper, times(1)).readXml(any(TypedXmlPullParser.class), anyLong());
     }
 
@@ -6372,7 +6373,7 @@
         mService.readPolicyXml(
                 new BufferedInputStream(new ByteArrayInputStream(preupgradeXml.getBytes())),
                 false,
-                UserHandle.USER_ALL);
+                UserHandle.USER_ALL, null);
         verify(mListeners, never()).readXml(any(), any(), anyBoolean(), anyInt());
         verify(mConditionProviders, never()).readXml(any(), any(), anyBoolean(), anyInt());
         verify(mAssistants, never()).readXml(any(), any(), anyBoolean(), anyInt());
@@ -6404,7 +6405,7 @@
         mService.readPolicyXml(
                 new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())),
                 true,
-                10);
+                10, null);
         verify(mListeners, never()).readXml(any(), any(), eq(true), eq(10));
         verify(mConditionProviders, never()).readXml(any(), any(), eq(true), eq(10));
         verify(mAssistants, never()).readXml(any(), any(), eq(true), eq(10));
@@ -6430,7 +6431,7 @@
         mService.readPolicyXml(
                 new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())),
                 true,
-                10);
+                10, null);
         verify(mListeners, never()).readXml(any(), any(), eq(true), eq(10));
         verify(mConditionProviders, never()).readXml(any(), any(), eq(true), eq(10));
         verify(mAssistants, never()).readXml(any(), any(), eq(true), eq(10));
@@ -6458,7 +6459,7 @@
         mService.readPolicyXml(
                 new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())),
                 true,
-                10);
+                10, null);
         verify(mListeners, never()).readXml(any(), any(), eq(true), eq(10));
         verify(mConditionProviders, never()).readXml(any(), any(), eq(true), eq(10));
         verify(mAssistants, never()).readXml(any(), any(), eq(true), eq(10));
@@ -6485,7 +6486,7 @@
         mService.readPolicyXml(
                 new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())),
                 true,
-                10);
+                10, null);
         verify(mListeners, times(1)).readXml(any(), any(), eq(true), eq(10));
         verify(mConditionProviders, times(1)).readXml(any(), any(), eq(true), eq(10));
         verify(mAssistants, times(1)).readXml(any(), any(), eq(true), eq(10));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 50a5f65..4391152 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -546,7 +546,8 @@
     }
 
     @Test
-    @EnableFlags(com.android.server.notification.Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI)
+    @EnableFlags(com.android.server.notification.Flags
+            .FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI_FOR_CHANNEL)
     public void testVibration_customVibrationForSound_withVibrationUri() throws IOException {
         defaultChannel.enableVibration(true);
         VibrationInfo vibration = getTestingVibration(mVibrator);
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
index 3910904..7509681 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
@@ -683,6 +683,41 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_BAL_ADDITIONAL_START_MODES)
+    public void testRealCaller_sawPermission() {
+        int callingUid = REGULAR_UID_1;
+        int callingPid = REGULAR_PID_1;
+        final String callingPackage = REGULAR_PACKAGE_1;
+        int realCallingUid = REGULAR_UID_2;
+        int realCallingPid = REGULAR_PID_2;
+
+        // setup state
+        when(mService.hasSystemAlertWindowPermission(eq(realCallingUid), eq(realCallingPid),
+                any())).thenReturn(true);
+
+        // prepare call
+        PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+        BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+        Intent intent = TEST_INTENT;
+        ActivityOptions checkedOptions =
+                mCheckedOptions.setPendingIntentBackgroundActivityStartMode(
+                        MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
+        BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
+                callingPid, callingPackage, realCallingUid, realCallingPid, null,
+                originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+                checkedOptions);
+
+        // call
+        BalVerdict callerVerdict = mController.checkBackgroundActivityStartAllowedByRealCaller(
+                balState);
+        balState.setResultForCaller(callerVerdict);
+
+        // assertions
+        assertWithMessage(balState.toString()).that(callerVerdict.getCode()).isEqualTo(
+                BAL_ALLOW_SAW_PERMISSION);
+    }
+
+    @Test
     public void testCaller_isRecents() {
         int callingUid = REGULAR_UID_1;
         int callingPid = REGULAR_PID_1;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 72f4fa91..c1edae9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4883,7 +4883,7 @@
 
 
     @Test
-    @EnableCompatChanges({ActivityRecord.UNIVERSAL_RESIZABLE_BY_DEFAULT})
+    @EnableCompatChanges({ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT})
     public void testUniversalResizeableByDefault() {
         mSetFlagsRule.enableFlags(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT);
         mDisplayContent.setIgnoreOrientationRequest(false);
diff --git a/services/usb/java/com/android/server/usb/UsbManagerInternal.java b/services/usb/java/com/android/server/usb/UsbManagerInternal.java
index c97df6b..31c5986 100644
--- a/services/usb/java/com/android/server/usb/UsbManagerInternal.java
+++ b/services/usb/java/com/android/server/usb/UsbManagerInternal.java
@@ -34,9 +34,11 @@
 public abstract class UsbManagerInternal {
 
   public static final int OS_USB_DISABLE_REASON_AAPM = 0;
+  public static final int OS_USB_DISABLE_REASON_LOCKDOWN_MODE = 1;
 
   @Retention(RetentionPolicy.SOURCE)
-  @IntDef(value = {OS_USB_DISABLE_REASON_AAPM})
+  @IntDef(value = {OS_USB_DISABLE_REASON_AAPM,
+    OS_USB_DISABLE_REASON_LOCKDOWN_MODE})
   public @interface OsUsbDisableReason {
   }
 
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index ba9dff6..ec4f7e1 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -1527,8 +1527,11 @@
             }
             mLockdownModeStatus = lockDownTriggeredByUser;
             for (UsbPort port: mPortManager.getPorts()) {
-                enableUsbData(port.getId(), !lockDownTriggeredByUser, STRONG_AUTH_OPERATION_ID,
-                        new IUsbOperationInternal.Default());
+                enableUsbDataInternal(port.getId(), !lockDownTriggeredByUser,
+                    STRONG_AUTH_OPERATION_ID,
+                    new IUsbOperationInternal.Default(),
+                    UsbManagerInternal.OS_USB_DISABLE_REASON_LOCKDOWN_MODE,
+                    true);
             }
         }
     }
diff --git a/telecomm/java/android/telecom/Logging/Session.java b/telecomm/java/android/telecom/Logging/Session.java
index e2fb601..ad0d4f4 100644
--- a/telecomm/java/android/telecom/Logging/Session.java
+++ b/telecomm/java/android/telecom/Logging/Session.java
@@ -16,7 +16,6 @@
 
 package android.telecom.Logging;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -24,8 +23,13 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.flags.Flags;
 
+import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Stores information about a thread's point of entry into that should persist until that thread
@@ -55,7 +59,7 @@
      * Initial value of mExecutionEndTimeMs and the final value of {@link #getLocalExecutionTime()}
      * if the Session is canceled.
      */
-    public static final int UNDEFINED = -1;
+    public static final long UNDEFINED = -1;
 
     public static class Info implements Parcelable {
         public final String sessionId;
@@ -129,46 +133,39 @@
         }
     }
 
-    private String mSessionId;
-    private String mShortMethodName;
+    private final String mSessionId;
+    private volatile String mShortMethodName;
     private long mExecutionStartTimeMs;
     private long mExecutionEndTimeMs = UNDEFINED;
-    private Session mParentSession;
-    private ArrayList<Session> mChildSessions;
+    private volatile Session mParentSession;
+    private final ArrayList<Session> mChildSessions = new ArrayList<>(5);
     private boolean mIsCompleted = false;
-    private boolean mIsExternal = false;
-    private int mChildCounter = 0;
+    private final boolean mIsExternal;
+    private final AtomicInteger mChildCounter = new AtomicInteger(0);
     // True if this is a subsession that has been started from the same thread as the parent
     // session. This can happen if Log.startSession(...) is called multiple times on the same
     // thread in the case of one Telecom entry point method calling another entry point method.
     // In this case, we can just make this subsession "invisible," but still keep track of it so
     // that the Log.endSession() calls match up.
-    private boolean mIsStartedFromActiveSession = false;
+    private final boolean mIsStartedFromActiveSession;
     // Optionally provided info about the method/class/component that started the session in order
     // to make Logging easier. This info will be provided in parentheses along with the session.
-    private String mOwnerInfo;
+    private final String mOwnerInfo;
     // Cache Full Method path so that recursive population of the full method path only needs to
     // be calculated once.
-    private String mFullMethodPathCache;
+    private volatile String mFullMethodPathCache;
 
     public Session(String sessionId, String shortMethodName, long startTimeMs,
-            boolean isStartedFromActiveSession, String ownerInfo) {
-        setSessionId(sessionId);
+            boolean isStartedFromActiveSession, boolean isExternal, String ownerInfo) {
+        mSessionId = (sessionId != null) ? sessionId : "???";
         setShortMethodName(shortMethodName);
         mExecutionStartTimeMs = startTimeMs;
         mParentSession = null;
-        mChildSessions = new ArrayList<>(5);
         mIsStartedFromActiveSession = isStartedFromActiveSession;
+        mIsExternal = isExternal;
         mOwnerInfo = ownerInfo;
     }
 
-    public void setSessionId(@NonNull String sessionId) {
-        if (sessionId == null) {
-            mSessionId = "?";
-        }
-        mSessionId = sessionId;
-    }
-
     public String getShortMethodName() {
         return mShortMethodName;
     }
@@ -180,10 +177,6 @@
         mShortMethodName = shortMethodName;
     }
 
-    public void setIsExternal(boolean isExternal) {
-        mIsExternal = isExternal;
-    }
-
     public boolean isExternal() {
         return mIsExternal;
     }
@@ -193,13 +186,15 @@
     }
 
     public void addChild(Session childSession) {
-        if (childSession != null) {
+        if (childSession == null) return;
+        synchronized (mChildSessions) {
             mChildSessions.add(childSession);
         }
     }
 
     public void removeChild(Session child) {
-        if (child != null) {
+        if (child == null) return;
+        synchronized (mChildSessions) {
             mChildSessions.remove(child);
         }
     }
@@ -217,7 +212,9 @@
     }
 
     public ArrayList<Session> getChildSessions() {
-        return mChildSessions;
+        synchronized (mChildSessions) {
+            return new ArrayList<>(mChildSessions);
+        }
     }
 
     public boolean isSessionCompleted() {
@@ -259,17 +256,41 @@
         return mExecutionEndTimeMs - mExecutionStartTimeMs;
     }
 
-    public synchronized String getNextChildId() {
-        return String.valueOf(mChildCounter++);
+    public String getNextChildId() {
+        return String.valueOf(mChildCounter.getAndIncrement());
     }
 
-    // Builds full session id recursively
+    // Builds full session ID, which incliudes the optional external indicators (E),
+    // base session ID, and the optional sub-session IDs (_X): @[E-]...[ID][_X][_Y]...
     private String getFullSessionId() {
-        return getFullSessionId(0);
+        if (!Flags.endSessionImprovements()) return getFullSessionIdRecursive(0);
+        int currParentCount = 0;
+        StringBuilder id = new StringBuilder();
+        Session currSession = this;
+        while (currSession != null) {
+            Session parentSession = currSession.getParentSession();
+            if (parentSession != null) {
+                if (currParentCount >= SESSION_RECURSION_LIMIT) {
+                    id.insert(0, getSessionId());
+                    id.insert(0, TRUNCATE_STRING);
+                    android.util.Slog.w(LOG_TAG, "getFullSessionId: Hit iteration limit!");
+                    return id.toString();
+                }
+                if (Log.VERBOSE) {
+                    id.insert(0, currSession.getSessionId());
+                    id.insert(0, SESSION_SEPARATION_CHAR_CHILD);
+                }
+            } else {
+                id.insert(0, currSession.getSessionId());
+            }
+            currSession = parentSession;
+            currParentCount++;
+        }
+        return id.toString();
     }
 
     // keep track of calls and bail if we hit the recursion limit
-    private String getFullSessionId(int parentCount) {
+    private String getFullSessionIdRecursive(int parentCount) {
         if (parentCount >= SESSION_RECURSION_LIMIT) {
             // Don't use Telecom's Log.w here or it will cause infinite recursion because it will
             // try to add session information to this logging statement, which will cause it to hit
@@ -286,12 +307,12 @@
             return mSessionId;
         } else {
             if (Log.VERBOSE) {
-                return parentSession.getFullSessionId(parentCount + 1)
+                return parentSession.getFullSessionIdRecursive(parentCount + 1)
                         // Append "_X" to subsession to show subsession designation.
                         + SESSION_SEPARATION_CHAR_CHILD + mSessionId;
             } else {
                 // Only worry about the base ID at the top of the tree.
-                return parentSession.getFullSessionId(parentCount + 1);
+                return parentSession.getFullSessionIdRecursive(parentCount + 1);
             }
 
         }
@@ -300,16 +321,18 @@
     private Session getRootSession(String callingMethod) {
         int currParentCount = 0;
         Session topNode = this;
-        while (topNode.getParentSession() != null) {
+        Session parentNode = topNode.getParentSession();
+        while (parentNode != null) {
             if (currParentCount >= SESSION_RECURSION_LIMIT) {
                 // Don't use Telecom's Log.w here or it will cause infinite recursion because it
                 // will try to add session information to this logging statement, which will cause
                 // it to hit this condition again and so on...
-                android.util.Slog.w(LOG_TAG, "getRootSession: Hit recursion limit from "
+                android.util.Slog.w(LOG_TAG, "getRootSession: Hit iteration limit from "
                         + callingMethod);
                 break;
             }
-            topNode = topNode.getParentSession();
+            topNode = parentNode;
+            parentNode = topNode.getParentSession();
             currParentCount++;
         }
         return topNode;
@@ -320,14 +343,40 @@
         return getRootSession("printFullSessionTree").printSessionTree();
     }
 
-    // Recursively move down session tree using DFS, but print out each node when it is reached.
     private String printSessionTree() {
         StringBuilder sb = new StringBuilder();
-        printSessionTree(0, sb, 0);
+        if (!Flags.endSessionImprovements()) {
+            printSessionTreeRecursive(0, sb, 0);
+            return sb.toString();
+        }
+        int depth = 0;
+        ArrayDeque<Session> deque = new ArrayDeque<>();
+        deque.add(this);
+        while (!deque.isEmpty()) {
+            Session node = deque.pollFirst();
+            sb.append("\t".repeat(depth));
+            sb.append(node.toString());
+            sb.append("\n");
+            if (depth >= SESSION_RECURSION_LIMIT) {
+                sb.append(TRUNCATE_STRING);
+                depth -= 1;
+                continue;
+            }
+            List<Session> childSessions = node.getChildSessions().reversed();
+            if (!childSessions.isEmpty()) {
+                depth += 1;
+                for (Session child : childSessions) {
+                    deque.addFirst(child);
+                }
+            } else {
+                depth -= 1;
+            }
+        }
         return sb.toString();
     }
 
-    private void printSessionTree(int tabI, StringBuilder sb, int currChildCount) {
+    // Recursively move down session tree using DFS, but print out each node when it is reached.
+    private void printSessionTreeRecursive(int tabI, StringBuilder sb, int currChildCount) {
         // Prevent infinite recursion.
         if (currChildCount >= SESSION_RECURSION_LIMIT) {
             // Don't use Telecom's Log.w here or it will cause infinite recursion because it will
@@ -343,26 +392,85 @@
             for (int i = 0; i <= tabI; i++) {
                 sb.append("\t");
             }
-            child.printSessionTree(tabI + 1, sb, currChildCount + 1);
+            child.printSessionTreeRecursive(tabI + 1, sb, currChildCount + 1);
         }
     }
 
-    // Recursively concatenate mShortMethodName with the parent Sessions to create full method
-    // path. if truncatePath is set to true, all other external sessions (except for the most
-    // recent) will be truncated to "..."
+    //
+
+    /**
+     * Concatenate the short method name with the parent Sessions to create full method path.
+     * @param truncatePath if truncatePath is set to true, all other external sessions (except for
+     *                     the most recent) will be truncated to "..."
+     * @return The full method path associated with this Session.
+     */
+    @VisibleForTesting
     public String getFullMethodPath(boolean truncatePath) {
         StringBuilder sb = new StringBuilder();
-        getFullMethodPath(sb, truncatePath, 0);
+        if (!Flags.endSessionImprovements()) {
+            getFullMethodPathRecursive(sb, truncatePath, 0);
+            return sb.toString();
+        }
+        // Check to see if the session has been renamed yet. If it has not, then the session
+        // has not been continued.
+        Session parentSession = getParentSession();
+        boolean isSessionStarted = parentSession == null
+                || !getShortMethodName().equals(parentSession.getShortMethodName());
+        int depth = 0;
+        Session currSession = this;
+        while (currSession != null) {
+            String cache = currSession.mFullMethodPathCache;
+            // Return cached value for method path. When returning the truncated path, recalculate
+            // the full path without using the cached value.
+            if (!TextUtils.isEmpty(cache) && !truncatePath) {
+                sb.insert(0, cache);
+                return sb.toString();
+            }
+
+            parentSession = currSession.getParentSession();
+            // Encapsulate the external session's method name so it is obvious what part of the
+            // session is external or truncate it if we do not want the entire history.
+            if (currSession.isExternal()) {
+                if (truncatePath) {
+                    sb.insert(0, TRUNCATE_STRING);
+                } else {
+                    sb.insert(0, ")");
+                    sb.insert(0, currSession.getShortMethodName());
+                    sb.insert(0, "(");
+                }
+            } else {
+                sb.insert(0, currSession.getShortMethodName());
+            }
+            if (parentSession != null) {
+                sb.insert(0, SUBSESSION_SEPARATION_CHAR);
+            }
+
+            if (depth >= SESSION_RECURSION_LIMIT) {
+                // Don't use Telecom's Log.w here or it will cause infinite recursion because it
+                // will try to add session information to this logging statement, which will cause
+                // it to hit this condition again and so on...
+                android.util.Slog.w(LOG_TAG, "getFullMethodPath: Hit iteration limit!");
+                sb.insert(0, TRUNCATE_STRING);
+                return sb.toString();
+            }
+            currSession = parentSession;
+            depth++;
+        }
+        if (isSessionStarted && !truncatePath) {
+            // Cache the full method path for this node so that we do not need to calculate it
+            // again in the future.
+            mFullMethodPathCache = sb.toString();
+        }
         return sb.toString();
     }
 
-    private synchronized void getFullMethodPath(StringBuilder sb, boolean truncatePath,
+    private synchronized void getFullMethodPathRecursive(StringBuilder sb, boolean truncatePath,
             int parentCount) {
         if (parentCount >= SESSION_RECURSION_LIMIT) {
             // Don't use Telecom's Log.w here or it will cause infinite recursion because it will
             // try to add session information to this logging statement, which will cause it to hit
             // this condition again and so on...
-            android.util.Slog.w(LOG_TAG, "getFullMethodPath: Hit recursion limit!");
+            android.util.Slog.w(LOG_TAG, "getFullMethodPathRecursive: Hit recursion limit!");
             sb.append(TRUNCATE_STRING);
             return;
         }
@@ -378,7 +486,7 @@
             // Check to see if the session has been renamed yet. If it has not, then the session
             // has not been continued.
             isSessionStarted = !mShortMethodName.equals(parentSession.mShortMethodName);
-            parentSession.getFullMethodPath(sb, truncatePath, parentCount + 1);
+            parentSession.getFullMethodPathRecursive(sb, truncatePath, parentCount + 1);
             sb.append(SUBSESSION_SEPARATION_CHAR);
         }
         // Encapsulate the external session's method name so it is obvious what part of the session
@@ -409,14 +517,14 @@
 
     @Override
     public int hashCode() {
-        int result = mSessionId != null ? mSessionId.hashCode() : 0;
-        result = 31 * result + (mShortMethodName != null ? mShortMethodName.hashCode() : 0);
-        result = 31 * result + (int) (mExecutionStartTimeMs ^ (mExecutionStartTimeMs >>> 32));
-        result = 31 * result + (int) (mExecutionEndTimeMs ^ (mExecutionEndTimeMs >>> 32));
+        int result = mSessionId.hashCode();
+        result = 31 * result + mShortMethodName.hashCode();
+        result = 31 * result + Long.hashCode(mExecutionStartTimeMs);
+        result = 31 * result + Long.hashCode(mExecutionEndTimeMs);
         result = 31 * result + (mParentSession != null ? mParentSession.hashCode() : 0);
-        result = 31 * result + (mChildSessions != null ? mChildSessions.hashCode() : 0);
+        result = 31 * result + mChildSessions.hashCode();
         result = 31 * result + (mIsCompleted ? 1 : 0);
-        result = 31 * result + mChildCounter;
+        result = 31 * result + mChildCounter.hashCode();
         result = 31 * result + (mIsStartedFromActiveSession ? 1 : 0);
         result = 31 * result + (mOwnerInfo != null ? mOwnerInfo.hashCode() : 0);
         return result;
@@ -432,23 +540,13 @@
         if (mExecutionStartTimeMs != session.mExecutionStartTimeMs) return false;
         if (mExecutionEndTimeMs != session.mExecutionEndTimeMs) return false;
         if (mIsCompleted != session.mIsCompleted) return false;
-        if (mChildCounter != session.mChildCounter) return false;
+        if (!(mChildCounter.get() == session.mChildCounter.get())) return false;
         if (mIsStartedFromActiveSession != session.mIsStartedFromActiveSession) return false;
-        if (mSessionId != null ?
-                !mSessionId.equals(session.mSessionId) : session.mSessionId != null)
-            return false;
-        if (mShortMethodName != null ? !mShortMethodName.equals(session.mShortMethodName)
-                : session.mShortMethodName != null)
-            return false;
-        if (mParentSession != null ? !mParentSession.equals(session.mParentSession)
-                : session.mParentSession != null)
-            return false;
-        if (mChildSessions != null ? !mChildSessions.equals(session.mChildSessions)
-                : session.mChildSessions != null)
-            return false;
-        return mOwnerInfo != null ? mOwnerInfo.equals(session.mOwnerInfo)
-                : session.mOwnerInfo == null;
-
+        if (!Objects.equals(mSessionId, session.mSessionId)) return false;
+        if (!Objects.equals(mShortMethodName, session.mShortMethodName)) return false;
+        if (!Objects.equals(mParentSession, session.mParentSession)) return false;
+        if (!Objects.equals(mChildSessions, session.mChildSessions)) return false;
+        return Objects.equals(mOwnerInfo, session.mOwnerInfo);
     }
 
     @Override
diff --git a/telecomm/java/android/telecom/Logging/SessionManager.java b/telecomm/java/android/telecom/Logging/SessionManager.java
index 9d17219..00e344c 100644
--- a/telecomm/java/android/telecom/Logging/SessionManager.java
+++ b/telecomm/java/android/telecom/Logging/SessionManager.java
@@ -27,6 +27,7 @@
 import android.util.Base64;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.flags.Flags;
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
@@ -36,10 +37,16 @@
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
- * TODO: Create better Sessions Documentation
+ * SessionManager manages the active sessions in a HashMap, which maps the active thread(s) to the
+ * associated {@link Session}s.
+ * <p>
+ * Note: Sessions assume that session structure modification is synchronized on this object - only
+ * one thread can modify the structure of any Session at one time. Printing the current session to
+ * the log is not synchronized because we should not clean up a session chain while printing from
+ * another Thread. Either the Session chain is still active and can not be cleaned up yet, or the
+ * Session chain has ended and we are cleaning up.
  * @hide
  */
-
 public class SessionManager {
 
     // Currently using 3 letters, So don't exceed 64^3
@@ -54,11 +61,11 @@
     private Context mContext;
 
     @VisibleForTesting
-    public ConcurrentHashMap<Integer, Session> mSessionMapper = new ConcurrentHashMap<>(100);
+    public final ConcurrentHashMap<Integer, Session> mSessionMapper = new ConcurrentHashMap<>(64);
     @VisibleForTesting
     public java.lang.Runnable mCleanStaleSessions = () ->
             cleanupStaleSessions(getSessionCleanupTimeoutMs());
-    private Handler mSessionCleanupHandler = new Handler(Looper.getMainLooper());
+    private final Handler mSessionCleanupHandler = new Handler(Looper.getMainLooper());
 
     // Overridden in LogTest to skip query to ContentProvider
     private interface ISessionCleanupTimeoutMs {
@@ -83,7 +90,7 @@
     };
 
     // Usage is synchronized on this class.
-    private List<ISessionListener> mSessionListeners = new ArrayList<>();
+    private final List<ISessionListener> mSessionListeners = new ArrayList<>();
 
     public interface ISessionListener {
         /**
@@ -110,10 +117,19 @@
     }
 
     private synchronized void resetStaleSessionTimer() {
-        mSessionCleanupHandler.removeCallbacksAndMessages(null);
-        // Will be null in Log Testing
-        if (mCleanStaleSessions != null) {
-            mSessionCleanupHandler.postDelayed(mCleanStaleSessions, getSessionCleanupTimeoutMs());
+        if (!Flags.endSessionImprovements()) {
+            mSessionCleanupHandler.removeCallbacksAndMessages(null);
+            // Will be null in Log Testing
+            if (mCleanStaleSessions != null) {
+                mSessionCleanupHandler.postDelayed(mCleanStaleSessions,
+                        getSessionCleanupTimeoutMs());
+            }
+        } else {
+            if (mCleanStaleSessions != null
+                    && !mSessionCleanupHandler.hasCallbacks(mCleanStaleSessions)) {
+                mSessionCleanupHandler.postDelayed(mCleanStaleSessions,
+                        getSessionCleanupTimeoutMs());
+            }
         }
     }
 
@@ -147,13 +163,11 @@
             Session childSession = createSubsession(true);
             continueSession(childSession, shortMethodName);
             return;
-        } else {
-            // Only Log that we are starting the parent session.
-            Log.d(LOGGING_TAG, Session.START_SESSION);
         }
         Session newSession = new Session(getNextSessionID(), shortMethodName,
-                System.currentTimeMillis(), false, callerIdentification);
+                System.currentTimeMillis(), false, false, callerIdentification);
         mSessionMapper.put(threadId, newSession);
+        Log.d(LOGGING_TAG, Session.START_SESSION);
     }
 
     /**
@@ -179,17 +193,16 @@
         }
 
         // Create Session from Info and add to the sessionMapper under this ID.
-        Log.d(LOGGING_TAG, Session.START_EXTERNAL_SESSION);
         Session externalSession = new Session(Session.EXTERNAL_INDICATOR + sessionInfo.sessionId,
-                sessionInfo.methodPath, System.currentTimeMillis(),
-                false /*isStartedFromActiveSession*/, sessionInfo.ownerInfo);
-        externalSession.setIsExternal(true);
+                sessionInfo.methodPath, System.currentTimeMillis(), false, true,
+                sessionInfo.ownerInfo);
         // Mark the external session as already completed, since we have no way of knowing when
         // the external session actually has completed.
         externalSession.markSessionCompleted(Session.UNDEFINED);
         // Track the external session with the SessionMapper so that we can create and continue
         // an active subsession based on it.
         mSessionMapper.put(threadId, externalSession);
+        Log.d(LOGGING_TAG, Session.START_EXTERNAL_SESSION);
         // Create a subsession from this external Session parent node
         Session childSession = createSubsession();
         continueSession(childSession, shortMethodName);
@@ -226,13 +239,12 @@
         // Start execution time of the session will be overwritten in continueSession(...).
         Session newSubsession = new Session(threadSession.getNextChildId(),
                 threadSession.getShortMethodName(), System.currentTimeMillis(),
-                isStartedFromActiveSession, threadSession.getOwnerInfo());
+                isStartedFromActiveSession, false, threadSession.getOwnerInfo());
         threadSession.addChild(newSubsession);
         newSubsession.setParentSession(threadSession);
 
         if (!isStartedFromActiveSession) {
-            Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION + " " +
-                    newSubsession.toString());
+            Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION);
         } else {
             Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION +
                     " (Invisible subsession)");
@@ -273,7 +285,7 @@
         }
 
         subsession.markSessionCompleted(Session.UNDEFINED);
-        endParentSessions(subsession);
+        cleanupSessionTreeAndNotify(subsession);
     }
 
     /**
@@ -328,7 +340,7 @@
         // Remove after completed so that reference still exists for logging the end events
         Session parentSession = completedSession.getParentSession();
         mSessionMapper.remove(threadId);
-        endParentSessions(completedSession);
+        cleanupSessionTreeAndNotify(completedSession);
         // If this subsession was started from a parent session using Log.startSession, return the
         // ThreadID back to the parent after completion.
         if (parentSession != null && !parentSession.isSessionCompleted() &&
@@ -337,8 +349,49 @@
         }
     }
 
+    /**
+     * Move up the session tree and remove completed sessions until we either hit a session that was
+     * not completed yet or we reach the root node. Once we reach the root node, we will report the
+     * session times to session complete listeners.
+     * @param session The Session to clean up.
+     */
+    private void cleanupSessionTreeAndNotify(Session session) {
+        if (session == null) return;
+        if (!Flags.endSessionImprovements()) {
+            endParentSessionsRecursive(session);
+            return;
+        }
+        Session currSession = session;
+        // Traverse upwards and unlink until we either hit the root node or a node that isn't
+        // complete yet.
+        while (currSession != null) {
+            if (!currSession.isSessionCompleted() || !currSession.getChildSessions().isEmpty()) {
+                // We will return once the active session is completed.
+                return;
+            }
+            Session parentSession = currSession.getParentSession();
+            currSession.setParentSession(null);
+            // The session is either complete when we have reached the top node or we have reached
+            // the node where the parent is external. We only want to report the time it took to
+            // complete the local session, so for external nodes, report finished when the sub-node
+            // completes.
+            boolean reportSessionComplete =
+                    (parentSession == null && !currSession.isExternal())
+                            || (parentSession != null && parentSession.isExternal());
+            if (parentSession != null) parentSession.removeChild(currSession);
+            if (reportSessionComplete) {
+                long fullSessionTimeMs = System.currentTimeMillis()
+                        - currSession.getExecutionStartTimeMilliseconds();
+                Log.d(LOGGING_TAG, Session.END_SESSION + " (dur: " + fullSessionTimeMs
+                        + " ms): " + currSession);
+                notifySessionCompleteListeners(currSession.getShortMethodName(), fullSessionTimeMs);
+            }
+            currSession = parentSession;
+        }
+    }
+
     // Recursively deletes all complete parent sessions of the current subsession if it is a leaf.
-    private void endParentSessions(Session subsession) {
+    private void endParentSessionsRecursive(Session subsession) {
         // Session is not completed or not currently a leaf, so we can not remove because a child is
         // still running
         if (!subsession.isSessionCompleted() || subsession.getChildSessions().size() != 0) {
@@ -355,7 +408,7 @@
                         System.currentTimeMillis() - subsession.getExecutionStartTimeMilliseconds();
                 notifySessionCompleteListeners(subsession.getShortMethodName(), fullSessionTimeMs);
             }
-            endParentSessions(parentSession);
+            endParentSessionsRecursive(parentSession);
         } else {
             // All of the subsessions have been completed and it is time to report on the full
             // running time of the session.
@@ -370,8 +423,10 @@
     }
 
     private void notifySessionCompleteListeners(String methodName, long sessionTimeMs) {
-        for (ISessionListener l : mSessionListeners) {
-            l.sessionComplete(methodName, sessionTimeMs);
+        synchronized (mSessionListeners) {
+            for (ISessionListener l : mSessionListeners) {
+                l.sessionComplete(methodName, sessionTimeMs);
+            }
         }
     }
 
@@ -380,8 +435,8 @@
         return currentSession != null ? currentSession.toString() : "";
     }
 
-    public synchronized void registerSessionListener(ISessionListener l) {
-        if (l != null) {
+    public void registerSessionListener(ISessionListener l) {
+        synchronized (mSessionListeners) {
             mSessionListeners.add(l);
         }
     }
@@ -425,25 +480,30 @@
 
     @VisibleForTesting
     public synchronized void cleanupStaleSessions(long timeoutMs) {
-        String logMessage = "Stale Sessions Cleaned:\n";
+        StringBuilder logMessage = new StringBuilder("Stale Sessions Cleaned:");
         boolean isSessionsStale = false;
         long currentTimeMs = System.currentTimeMillis();
         // Remove references that are in the Session Mapper (causing GC to occur) on
-        // sessions that are lasting longer than LOGGING_SESSION_TIMEOUT_MS.
+        // sessions that are lasting longer than DEFAULT_SESSION_TIMEOUT_MS.
         // If this occurs, then there is most likely a Session active that never had
         // Log.endSession called on it.
         for (Iterator<ConcurrentHashMap.Entry<Integer, Session>> it =
              mSessionMapper.entrySet().iterator(); it.hasNext(); ) {
             ConcurrentHashMap.Entry<Integer, Session> entry = it.next();
             Session session = entry.getValue();
-            if (currentTimeMs - session.getExecutionStartTimeMilliseconds() > timeoutMs) {
+            long runTime = currentTimeMs - session.getExecutionStartTimeMilliseconds();
+            if (runTime > timeoutMs) {
                 it.remove();
-                logMessage += session.printFullSessionTree() + "\n";
+                logMessage.append("\n");
+                logMessage.append("[");
+                logMessage.append(runTime);
+                logMessage.append("ms] ");
+                logMessage.append(session.printFullSessionTree());
                 isSessionsStale = true;
             }
         }
         if (isSessionsStale) {
-            Log.w(LOGGING_TAG, logMessage);
+            Log.w(LOGGING_TAG, logMessage.toString());
         } else {
             Log.v(LOGGING_TAG, "No stale logging sessions needed to be cleaned...");
         }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 47f6764..02999c8 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -253,7 +253,6 @@
      *
      * The default value is true.
      */
-    @FlaggedApi(Flags.FLAG_SHOW_CALL_ID_AND_CALL_WAITING_IN_ADDITIONAL_SETTINGS_MENU)
     public static final String KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL =
             "additional_settings_caller_id_visibility_bool";
 
@@ -263,7 +262,6 @@
      *
      * The default value is true.
      */
-    @FlaggedApi(Flags.FLAG_SHOW_CALL_ID_AND_CALL_WAITING_IN_ADDITIONAL_SETTINGS_MENU)
     public static final String KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL =
             "additional_settings_call_waiting_visibility_bool";
 
@@ -10558,7 +10556,6 @@
      * @see SubscriptionInfo#getServiceCapabilities()
      * @see SubscriptionManager.OnSubscriptionsChangedListener
      */
-    @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
     public static final String KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY =
             "cellular_service_capabilities_int_array";
    /**
@@ -11110,7 +11107,7 @@
         sDefaults.putString(KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, "");
         sDefaults.putInt(KEY_NR_ADVANCED_BANDS_SECONDARY_TIMER_SECONDS_INT, 0);
         sDefaults.putBoolean(KEY_NR_TIMERS_RESET_IF_NON_ENDC_AND_RRC_IDLE_BOOL, false);
-        sDefaults.putBoolean(KEY_NR_TIMERS_RESET_ON_VOICE_QOS_BOOL, true);
+        sDefaults.putBoolean(KEY_NR_TIMERS_RESET_ON_VOICE_QOS_BOOL, false);
         sDefaults.putBoolean(KEY_NR_TIMERS_RESET_ON_PLMN_CHANGE_BOOL, false);
         /* Default value is 1 hour. */
         sDefaults.putLong(KEY_5G_WATCHDOG_TIME_MS_LONG, 3600000);
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 1089602..d164c88 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -951,7 +951,6 @@
      * @see SubscriptionManager#SERVICE_CAPABILITY_DATA
      */
     @NonNull
-    @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
     public @SubscriptionManager.ServiceCapability Set<Integer> getServiceCapabilities() {
         return SubscriptionManager.getServiceCapabilitiesSet(mServiceCapabilities);
     }
@@ -1829,7 +1828,6 @@
          * @throws IllegalArgumentException when any capability is not supported.
          */
         @NonNull
-        @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
         public Builder setServiceCapabilities(
                 @NonNull @SubscriptionManager.ServiceCapability Set<Integer> capabilities) {
             int combinedCapabilities = 0;
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 6faef7e..377e5f2 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1422,7 +1422,6 @@
      *
      * @see TelephonyManager#isDeviceVoiceCapable()
      */
-    @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
     public static final int SERVICE_CAPABILITY_VOICE = 1;
 
     /**
@@ -1440,13 +1439,11 @@
      *
      * @see TelephonyManager#isDeviceSmsCapable()
      */
-    @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
     public static final int SERVICE_CAPABILITY_SMS = 2;
 
     /**
      * Represents a value indicating the data calling capabilities of a subscription.
      */
-    @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
     public static final int SERVICE_CAPABILITY_DATA = 3;
 
     /**
@@ -3474,14 +3471,62 @@
     @SystemApi
     public boolean canManageSubscription(@NonNull SubscriptionInfo info,
             @NonNull String packageName) {
+        if (Flags.hsumPackageManager()) {
+            return canManageSubscriptionAsUser(info, packageName, mContext.getUser());
+        } else {
+            if (info == null || info.getAccessRules() == null || packageName == null) {
+                return false;
+            }
+            PackageManager packageManager = mContext.getPackageManager();
+            PackageInfo packageInfo;
+            try {
+                packageInfo = packageManager.getPackageInfo(packageName,
+                        PackageManager.GET_SIGNING_CERTIFICATES);
+            } catch (PackageManager.NameNotFoundException e) {
+                logd("Unknown package: " + packageName);
+                return false;
+            }
+            for (UiccAccessRule rule : info.getAccessRules()) {
+                if (rule.getCarrierPrivilegeStatus(packageInfo)
+                        == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Checks whether the given app is authorized to manage the given subscription for given user.
+     *
+     * <p>An app can only be authorized if it is available to the given user and included in the
+     * {@link android.telephony.UiccAccessRule} of the {@link android.telephony.SubscriptionInfo}
+     * with the access status.
+     *
+     * <p>Only supported for embedded subscriptions (if {@link SubscriptionInfo#isEmbedded} returns
+     * true). To check for permissions for non-embedded subscription as well,
+     * see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}.
+     *
+     * @param info        The subscription to check.
+     * @param packageName Package name of the app to check.
+     * @param user        UserHandle to check
+     * @return whether the app is authorized to manage this subscription per its access rules.
+     *
+     * @see android.telephony.TelephonyManager#hasCarrierPrivileges
+     * @hide
+     */
+    public boolean canManageSubscriptionAsUser(@NonNull SubscriptionInfo info,
+            @NonNull String packageName, @NonNull UserHandle user) {
         if (info == null || info.getAccessRules() == null || packageName == null) {
             return false;
         }
-        PackageManager packageManager = mContext.getPackageManager();
+        PackageManager pm = mContext.getUser().equals(user)
+                ? mContext.getPackageManager()
+                : mContext.createContextAsUser(user, 0).getPackageManager();
         PackageInfo packageInfo;
         try {
-            packageInfo = packageManager.getPackageInfo(packageName,
-                PackageManager.GET_SIGNING_CERTIFICATES);
+            packageInfo = pm.getPackageInfo(packageName,
+                    PackageManager.GET_SIGNING_CERTIFICATES);
         } catch (PackageManager.NameNotFoundException e) {
             logd("Unknown package: " + packageName);
             return false;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index fee4587..c8aa61c 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6934,7 +6934,6 @@
      *
      * @see SubscriptionInfo#getServiceCapabilities()
      */
-    @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
     public boolean isDeviceVoiceCapable() {
         return isVoiceCapable();
     }
@@ -6974,7 +6973,6 @@
      *
      * @see SubscriptionInfo#getServiceCapabilities()
      */
-    @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
     public boolean isDeviceSmsCapable() {
         return isSmsCapable();
     }
diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
index 0375f66..d9295dd 100644
--- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
@@ -515,33 +515,27 @@
                 Install.single(APEX_V2));
     }
 
-    @Test
-    public void testGetStagedModuleNames() throws Exception {
-        // Before staging a session
-        String[] result = getPackageManagerNative().getStagedApexModuleNames();
-        assertThat(result).hasLength(0);
-        // Stage an apex
-        int sessionId = Install.single(APEX_V2).setStaged().commit();
-        result = getPackageManagerNative().getStagedApexModuleNames();
-        assertThat(result).hasLength(1);
-        assertThat(result).isEqualTo(new String[]{SHIM_APEX_PACKAGE_NAME});
-        // Abandon the session
-        InstallUtils.openPackageInstallerSession(sessionId).abandon();
-        result = getPackageManagerNative().getStagedApexModuleNames();
-        assertThat(result).hasLength(0);
+    private StagedApexInfo findStagedApexInfo(StagedApexInfo[] infos, String moduleName) {
+        for (StagedApexInfo info: infos) {
+            if (info.moduleName.equals(moduleName)) {
+                return info;
+            }
+        }
+        return null;
     }
 
     @Test
-    public void testGetStagedApexInfo() throws Exception {
-        // Ask for non-existing module
-        StagedApexInfo result = getPackageManagerNative().getStagedApexInfo("not found");
-        assertThat(result).isNull();
+    public void testGetStagedApexInfos() throws Exception {
+        // Not found before staging
+        StagedApexInfo[] result = getPackageManagerNative().getStagedApexInfos();
+        assertThat(findStagedApexInfo(result, TEST_APEX_PACKAGE_NAME)).isNull();
         // Stage an apex
         int sessionId = Install.single(TEST_APEX_CLASSPATH).setStaged().commit();
         // Query proper module name
-        result = getPackageManagerNative().getStagedApexInfo(TEST_APEX_PACKAGE_NAME);
-        assertThat(result.moduleName).isEqualTo(TEST_APEX_PACKAGE_NAME);
-        assertThat(result.hasClassPathJars).isTrue();
+        result = getPackageManagerNative().getStagedApexInfos();
+        StagedApexInfo found = findStagedApexInfo(result, TEST_APEX_PACKAGE_NAME);
+        assertThat(found).isNotNull();
+        assertThat(found.hasClassPathJars).isTrue();
         InstallUtils.openPackageInstallerSession(sessionId).abandon();
     }
 
@@ -573,14 +567,15 @@
         int sessionId = Install.single(APEX_V2).setStaged().commit();
         ArgumentCaptor<ApexStagedEvent> captor = ArgumentCaptor.forClass(ApexStagedEvent.class);
         verify(observer, timeout(5000)).onApexStaged(captor.capture());
-        assertThat(captor.getValue().stagedApexModuleNames).isEqualTo(
-                new String[] {SHIM_APEX_PACKAGE_NAME});
+        StagedApexInfo found =
+                findStagedApexInfo(captor.getValue().stagedApexInfos, SHIM_APEX_PACKAGE_NAME);
+        assertThat(found).isNotNull();
 
         // Abandon and verify observer is called
         Mockito.clearInvocations(observer);
         InstallUtils.openPackageInstallerSession(sessionId).abandon();
         verify(observer, timeout(5000)).onApexStaged(captor.capture());
-        assertThat(captor.getValue().stagedApexModuleNames).hasLength(0);
+        assertThat(captor.getValue().stagedApexInfos).hasLength(0);
     }
 
     @Test
diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
index f1fc503..97abcd7 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -592,23 +592,15 @@
     }
 
     @Test
-    public void testGetStagedModuleNames() throws Exception {
-        assumeTrue("Device does not support updating APEX",
-                mHostUtils.isApexUpdateSupported());
-
-        runPhase("testGetStagedModuleNames");
-    }
-
-    @Test
     @LargeTest
-    public void testGetStagedApexInfo() throws Exception {
+    public void testGetStagedApexInfos() throws Exception {
         assumeTrue("Device does not support updating APEX",
                 mHostUtils.isApexUpdateSupported());
 
         pushTestApex(APEXD_TEST_APEX);
         getDevice().reboot();
 
-        runPhase("testGetStagedApexInfo");
+        runPhase("testGetStagedApexInfos");
     }
 
     @Test
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 4cb7c91..7e0bbc4 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -70,7 +70,6 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.Uri;
-import android.net.vcn.Flags;
 import android.net.vcn.IVcnStatusCallback;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
 import android.net.vcn.VcnConfig;
@@ -85,7 +84,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.test.TestLooper;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -104,7 +102,6 @@
 import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -122,8 +119,6 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class VcnManagementServiceTest {
-    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
     private static final String CONTEXT_ATTRIBUTION_TAG = "VCN";
     private static final String TEST_PACKAGE_NAME =
             VcnManagementServiceTest.class.getPackage().getName();
@@ -285,8 +280,6 @@
 
     @Before
     public void setUp() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_MAIN_USER);
-
         doNothing()
                 .when(mMockContext)
                 .enforceCallingOrSelfPermission(
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index edad678..421e1ad 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -34,14 +34,12 @@
 import android.net.NetworkCapabilities;
 import android.net.TelephonyNetworkSpecifier;
 import android.net.vcn.FeatureFlags;
-import android.net.vcn.Flags;
 import android.os.Handler;
 import android.os.IPowerManager;
 import android.os.IThermalService;
 import android.os.ParcelUuid;
 import android.os.PowerManager;
 import android.os.test.TestLooper;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.telephony.TelephonyManager;
 
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
@@ -49,7 +47,6 @@
 import com.android.server.vcn.VcnNetworkProvider;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -57,8 +54,6 @@
 import java.util.UUID;
 
 public abstract class NetworkEvaluationTestBase {
-    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
     protected static final String SSID = "TestWifi";
     protected static final String SSID_OTHER = "TestWifiOther";
     protected static final String PLMN_ID = "123456";
@@ -120,10 +115,6 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        mSetFlagsRule.enableFlags(Flags.FLAG_VALIDATE_NETWORK_ON_IPSEC_LOSS);
-        mSetFlagsRule.enableFlags(Flags.FLAG_EVALUATE_IPSEC_LOSS_ON_LP_NC_CHANGE);
-        mSetFlagsRule.enableFlags(Flags.FLAG_HANDLE_SEQ_NUM_LEAP);
-
         when(mNetwork.getNetId()).thenReturn(-1);
 
         mTestLooper = new TestLooper();
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index 4920f7b4..a5ff496 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -8,7 +8,7 @@
 
     // OWNER: g/ravenwood
     // Bug component: 25698
-    default_team: "trendy_team_framework_backstage_power",
+    default_team: "trendy_team_ravenwood",
 }
 
 // Visibility only for ravenwood prototype uses.